具有std :: minmax和rvalues

时间:2018-07-24 16:03:59

标签: c++ c++17 rvalue structured-bindings

在将std::minmax与结构化绑定一起使用时,我遇到了一个相当微妙的错误。似乎传递的右值不会总是像人们期望的那样被复制。最初,我在自定义容器上使用T operator[]() const,但它与文字整数似乎相同。

#include <algorithm>
#include <cstdio>
#include <tuple>

int main()
{
    auto [amin, amax] = std::minmax(3, 6);
    printf("%d,%d\n", amin, amax); // undefined,undefined

    int bmin, bmax;
    std::tie(bmin, bmax) = std::minmax(3, 6);
    printf("%d,%d\n", bmin, bmax); // 3,6
}

将GCC 8.1.1与-O1 -Wuninitialized一起使用将导致0,0被打印为第一行,并且:

warning: ‘<anonymous>’ is used uninitialized in this function [-Wuninitialized]

-O2处发出的6.0.1 lang语也会给出错误的第一结果,而不会发出警告。

-O0,GCC给出了正确的结果,没有警告。对于c,结果似乎是正确的-O1-O0

从右值仍可被复制的意义上说,第一行和第二行是否应该等效?

此外,为什么这取决于优化级别?特别让我惊讶的是,海湾合作委员会没有发出警告。

2 个答案:

答案 0 :(得分:10)

auto [amin, amax]中要注意的重要一点是autoauto&等适用于用返回值初始化的组成对象e std::minmax,是一对。本质上就是这样:

auto e = std::minmax(3, 6);

auto&& amin = std::get<0>(e);
auto&& amax = std::get<1>(e);

aminamax的实际类型是引用该对对象返回的std::get<0>std::get<1>返回值的引用。他们自己返回对早已消失的对象的引用!

使用std::tie时,您正在对现有对象进行分配(通过引用传递)。右值的生存时间不必长于它们产生的赋值表达式。


作为解决方法,您可以使用以下功能(而非生产质量):

template<typename T1, typename T2>
auto as_value(std::pair<T1, T2> in) {
    using U1 = std::decay_t<T1>;
    using U2 = std::decay_t<T2>;
    return std::pair<U1, U2>(in);
}

确保该对拥有值类型。像这样使用时:

auto [amin, amax] = as_value(std::minmax(3, 6));

我们现在获得了一个副本,结构化绑定引用了这些副本。

答案 1 :(得分:6)

这里有两个基本问题:

  1. minmaxminmax出于历史原因会返回引用。因此,如果您传入一个临时值,则最好按值获取结果或立即使用它,否则您将获得悬挂的引用。如果minmax在这里给您一个pair<int, int>而不是pair<int const&, int const&>,那么您不会有任何问题。
  2. auto使顶级的 cv 限定符衰减并去除引用,但并不能完全消除。在这里,您要推断出pair<int const&, int const&>,但是如果我们推断出pair<int, int>,我们将再没有任何问题。

(1)比(2)更容易解决:编写自己的函数以按值获取所有内容:

template <typename T>
std::pair<T, T> minmax(T a, T b) {
    return (b < a) ? std::pair(b, a) : std::pair(a, b);
}

auto [amin, amax] = minmax(3, 6); // no problems

按值获取一切的好处是,您不必担心隐藏的悬挂引用,因为没有任何引用。而且这些功能的绝大部分用途都是使用整数类型,因此引用毫无益处。

并且当您需要时需要引用,因为当您比较昂贵的复制对象时……好吧,采用一个带有值的函数并强制其使用引用比使用函数更容易这是一个使用引用的函数并尝试对其进行修复:

auto [lo, hi] = minmax(std::ref(big1), std::ref(big2)); 

此外,在呼叫站点上,我们正在使用引用,这很明显,所以如果搞砸了,它将更加明显。


由于reference_wrapper<T>隐式转换为T&,以上方法适用于许多类型,但不适用于具有非成员,非朋友,运算符模板的类型(像std::string)。因此,不幸的是,您还需要为参考包装编写一个专门化的文档。