【问题标题】:Is there a way to easily handle functions returning std::pairs?有没有办法轻松处理返回 std::pairs 的函数?
【发布时间】:2017-03-10 01:18:38
【问题描述】:

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()));

【问题讨论】:

  • 还没有人提到c++17? skebanga.github.io/structured-bindings
  • 一个问题:minmax_elementmin_elementmax_element 一起使用有效率优势吗?
  • @xaxxon:C++17 的特性不在 C++11 中。该问题标记为 C++11。所以这是一个有趣的切线,但不是一个实际的解决方案。
  • @AdamHunyadi 鉴于它是一个标准的 template 库,您自动拥有所有算法的源代码。我的是/usr/include/c++/6.2.1/bits/stl_algo.hminmax_element 只在该范围内迭代一次,所以是的,它比在 GCC 的实现中单独执行 min_elementmax_element 更有效。
  • 我认为你的“更好......类似”的代码并不是因为更长、有更多元素以及不必要地分离定义和初始化而变得更好。

标签: c++ c++11 tuples std-pair minmax


【解决方案1】:

使用 C++17 中的结构化绑定,您可以直接这样做

auto [lhsMinIt, lhsMaxIt] = std::minmax_element(lhs.begin(), lhs.end());

【讨论】:

  • 整洁!你能在一行中从迭代器对中取出值对吗?
  • @AdamHunyadi:C++1z 是 C++17 的“它还没有正式存在”的名称,就像 C++0x 如何变成 C++11(后期!)和 C ++1y 因为 C++14。
  • 赞成,因为这是一个有趣的讨论点,是一个很好的解决方案,但这实际上并不能回答问题(关于 C++11)。
  • @LightnessRacesinOrbit:C++1z 之后的标准是什么? C++2{? :-)
【解决方案2】:

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

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);
}();

【讨论】:

  • 应该注意的是,两者都不能像 OP 的第一部分那样分配引用。 (第二部分被破坏了,所以很难说,但它也暗示分配两个int&amp;s。)
  • 使用封闭范围和std::tie 比我认为的 lambda 更容易阅读。
  • 为什么在第一个例子的末尾有分号?
【解决方案3】:

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

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,但是完整的实现 can be made in C++11

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

See it live on Coliru

【讨论】:

  • SFINAE 还是不适用吗?
  • @LightnessRacesinOrbit 我重新检查以确定并确认:由于函数签名中没有任何限制T,因此在实例化函数主体时出现硬错误。见this quickly hacked test
  • 嗯,我猜 SFINAE 并没有像我想象的那样工作 :(
  • @ViktorSehr 你是什么意思? std::tie 返回 std::tuple&lt;T&amp;&gt;。但是,所有std::tuple 元函数,例如std::tuple_size,也可以与std::pair 一起使用。
  • @StoryTeller 确实如此,但变量的类型必须与该对元素的类型匹配。这里的问题是 OP 想要迭代器引用的值,而不是迭代器本身。您可以使用std::tie 并绑定到该对,但该对包含错误的值。
【解决方案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());

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


在 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 是对容器本身的引用。

【讨论】:

  • 您的deref 函数可能会导致未定义的行为。如果迭代器(或任何 args 最终成为)返回纯右值,你会得到悬空引用,因为 std::forward_as_tuple 为它们返回一个右值引用。
  • @Xeo 这取决于调用者是否保留引用。
  • 错了。您会在 return std::forward_as_tuple(...); 点获得悬空引用,因为临时对象是在该范围内创建的,并且在函数返回后立即死亡。
  • @Xeo 啊,最初误解了您的评论。现在应该修好了。
【解决方案5】:

在当前版本的标准中,没有办法同时分配两个引用,如果这是你所追求的。请注意,除了需要 C++17 和辅助模板的 Barry's 之外,没有其他答案可以做到这一点。

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

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

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

【讨论】:

    【解决方案6】:

    在 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 live example.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-03
      • 1970-01-01
      相关资源
      最近更新 更多