带有 std::minmax 和 rvalues 的结构化绑定
structured bindings with std::minmax and rvalues
我在将std::minmax
与结构化绑定一起使用时遇到了一个相当微妙的错误。似乎传递的右值并不总是像人们预期的那样被复制。最初我在自定义容器上使用T operator[]() const
,但它似乎与文字整数相同。
#include <algorithm>
#include <cstdio>
#include <tuple>
int main()
{
auto [amin, amax] = std::minmax(3, 6);
printf("%d,%dn", amin, amax); // undefined,undefined
int bmin, bmax;
std::tie(bmin, bmax) = std::minmax(3, 6);
printf("%d,%dn", bmin, bmax); // 3,6
}
将 GCC 8.1.1 与-O1 -Wuninitialized
一起使用将导致0,0
打印为第一行,并且:
warning: ‘<anonymous>’ is used uninitialized in this function [-Wuninitialized]
-O2
的 Clang 6.0.1 也会在没有警告的情况下给出错误的第一个结果。
在-O0
GCC给出正确的结果并且没有警告。对于 clang,结果在-O1
或-O0
处似乎是正确的。
第一行和第二行难道不应该等效,因为右值仍然有效,可以复制吗?
另外,为什么这取决于优化级别?我特别惊讶的是,海湾合作委员会没有发出警告。
在auto [amin, amax]
中需要注意的重要一点是,auto
、auto&
等应用于用返回值std::minmax
初始化的组成对象e
,这是一个对。本质上是这样的:
auto e = std::minmax(3, 6);
auto&& amin = std::get<0>(e);
auto&& amax = std::get<1>(e);
amin
和amax
的实际类型是引用,这些引用引用了该对对象的任何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));
我们现在制作了一个副本,结构化绑定引用了这些副本。
这里有两个基本问题:
min
、max
和minmax
由于历史原因返回引用。因此,如果您传入临时值,则最好按值获取结果或立即使用它,否则您将获得悬而未决的引用。如果minmax
在这里给你一个pair<int, int>
而不是一个pair<int const&, int const&>
,你就不会有任何问题。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
)的类型。因此,不幸的是,您还需要为引用包装器编写专门的内容。
- 在基于范围的for循环中使用结构化绑定声明
- 使用结构化绑定'Reflection'
- 为什么结构化绑定不使用"auto&"返回对结构成员的引用,而是返回成员本身
- 为什么 boost::comb 对结构化绑定的支持缺少结构化绑定机制对 boost::tuples::cons 的适应?
- 结构化绑定初始值设定项表单 { 赋值表达式 } 对于 clang 上的数组类型失败
- 在 C++14 中手动实现结构化绑定
- 为什么结构化绑定不支持可变数组?
- 为<vtkDataArray> VTK 非结构化网格声明 vtkSmartPointer 类型的变量时出现问题
- 在只读(即 const)访问器上执行结构化绑定的最佳实践是什么?
- 是否有像ADTF2这样的结构化属性?
- 带有 std::minmax 和 rvalues 的结构化绑定
- 在无序映射的结构化绑定中推导类型
- 为什么基于范围的 for 循环中的结构化绑定只是一个副本而不是引用?
- 您自己的类型的结构化绑定,不是结构或元组(通过公共成员函数)
- 共享或私有 openmp 代码中的结构化类型变量
- 结构化绑定:遍历元组的双端面
- 结构化绑定是否适用于 std::vector?
- 结构化绑定,无需复制即可获取子向量的连续元素
- 成员变量的结构化绑定
- 结构化绑定语法是否可以在多态 lambda 中使用