33

C++11 具有std::minmax_element返回一对值的函数。然而,这在处理和阅读时相当混乱,并且会产生一个额外的、后来无用的变量来污染作用域。

auto lhsMinmax = std::minmax_element(lhs.begin(), lhs.end());
int &lhsMin = *(lhsMinMax.first);
int &lhsMax = *(lhsMinmax.second);

有一个更好的方法吗?就像是:

int lhsMin;
int lhsMax;
std::make_pair<int&, int&>(lhsMin, lhsMax).swap(
    std::minmax_element(lhs.begin(), lhs.end()));
4

6 回答 6

32

使用C++17 的结构化绑定,您可以直接执行

auto [lhsMinIt, lhsMaxIt] = std::minmax_element(lhs.begin(), lhs.end());
于 2016-10-27T11:21:13.870 回答
23

为避免污染您的范围,您可以将分配包含在较小的范围内:

int lhsMin, lhsMax;

{
    auto it = std::minmax_element(lhs.begin(), lhs.end());
    lhsMin = *it.first;
    lhsMax = *it.second;
}

或者,您可以使用 lambda

int lhsMin, lhsMax;

std::tie(lhsMin, lhsMax) = [&]{
    auto it = std::minmax_element(lhs.begin(), lhs.end());
    return std::make_tuple(*it.first, *it.second);
}();
于 2016-10-27T11:16:07.557 回答
13

这看起来很常见,可以提示辅助函数:

template <class T, std::size_t...Idx>
auto deref_impl(T &&tuple, std::index_sequence<Idx...>) {
    return std::tuple<decltype(*std::get<Idx>(std::forward<T>(tuple)))...>(*std::get<Idx>(std::forward<T>(tuple))...);
}

template <class T>
auto deref(T &&tuple)
    -> decltype(deref_impl(std::forward<T>(tuple), std::make_index_sequence<std::tuple_size<std::remove_reference_t<T>>::value>{})) {
    return deref_impl(std::forward<T>(tuple), std::make_index_sequence<std::tuple_size<std::remove_reference_t<T>>::value>{});
}

// ...

int lhsMin;
int lhsMax;
std::tie(lhsMin,lhsMax) = deref(std::minmax_element(lhs.begin(), lhs.end()));

index_sequence是 C++14,但可以在 C++11 中进行完整的实现。

注意:即使在 C++14 中,我也会保留重复decltype的 inderef的返回类型,以便 SFINAE 可以应用。

在 Coliru 上现场观看

于 2016-10-27T11:36:29.030 回答
4

我会更直接并编写我自己的版本minmax_element

template <class Iter, class R = typename iterator_traits<Iter>::reference>
std::pair<R,R> deref_minmax(Iter first, Iter last)
{
    auto iters = std::minmax_element(first, last);
    return std::pair<R,R>{*iters.first, *iters.second};
}

那就是:

int lo, hi;
std::tie(lo, hi) = deref_minmax(lhs.begin(), lhs.end());

这会将您限制为元素的单个副本(这与 s 没什么大不了int的),还可以让您保持对实际容器的引用的访问。


在 C++17 中,为了好玩,我们可以编写一个通用的解引用器:

template <class Tuple>
auto deref(Tuple&& tup) {
    return std::apply([](auto... args) {
        return std::tuple <decltype(*args)...>(*args...);
    }, tup);
}

auto& [lo, hi] = deref(std::minmax_element(lhs.begin(), lhs.end()));

这里lohi是对容器本身的引用。

于 2016-10-27T22:03:25.180 回答
2

在 C++14 或更高版本中

template<class=void, std::size_t...Is>
auto indexer( std::index_sequence<Is...> ) {
  return [](auto&&f){
    return f( std::integral_constant<std::size_t, Is>{}... );
  };
}
template<std::size_t N>
auto indexer() {
  return indexer( std::make_index_sequence<N>{} );
}
template<class F>
auto fmap_over_tuple( F&& f ) {
  return [f=std::forward<F>(f)](auto&& tuple) {
    using Tuple = decltype(tuple);
    using Tuple_d = std::decay_t<Tuple>;
    auto index = indexer< std::tuple_size< Tuple_d >::value >();
    return index(
      [&f, &tuple](auto&&...Is) {
        using std::get;
        return std::make_tuple(
          f( get<Is>( std::forward<Tuple>(tuple) ) )...
        );
      }
    );
  };
}

所以fmap_over_tuple需要一个函数对象。它返回一个函数对象,当传递一个类元组时,继续调用类元组的每个元素上的函数对象,并从中生成一个元组。

然后我们编写解引用元组:

auto dereference_tuple = fmap_over_tuple(
  [](auto&& e) { return *e; }
);

现在在 C++17 中,我们这样做:

auto[Min, Max] = dereference_tuple( std::minmax_element(lhs.begin(), lhs.end() );

鲍勃是你的叔叔。

在 C++11 中,做你所做的。够干净。

C++14 实例

于 2016-10-28T00:01:04.040 回答
2

如果这是您所追求的,那么在标准的当前修订版中无法一次分配两个参考。请注意,除了需要 C++17 和辅助模板的 Barry's 之外,没有其他答案可以做到这一点。

但是,如果您想对最小和最大元素进行读写访问,为什么不直接使用迭代器minmax_element呢?无论如何,它可能会生成与使用引用相同的机器代码,至少如果您lhs是 aContiguousContainer但可能在其他情况下也是如此。

您将需要减少对自动类型推断的依赖,例如,

decltype(lhs.begin()) lhsMinIt, lhsMaxIt;
std::tie(lhsMinIt, lhsMaxIt) = std::minmax_element(lhs.begin(), lhs.end());
/* now access your minimum and maximum as *lhsMinIt and *lhsMaxIt */

如果您知道 的类型lhs将是标准容器之一,则可以使用更简洁的类型名称decltype(lhs)::iterator

于 2016-10-27T23:24:34.210 回答