为什么不能为 std::tuple 分配初始值设定项列表?
Why can a std::tuple not be assigned with an initializer list?
我想知道为什么会做出这样的选择。它将允许以非常清晰和整洁的方式编写许多功能.. 例如:
int greatestCommonDivisor(int a, int b)
{
if (b > a)
std::tie(a, b) = { b, a };
while (b > 0)
std::tie(a, b) = { b, a % b };
return a;
}
std::initializer_list
is a homogeneous collection of items, while std::tuple
is heterogeneous. The only case where it makes sense to define a std::tuple::operator=
for std::initializer_list
是当元组是同质的并且与初始化列表具有相同大小时,这种情况很少见。
(Additional information in this question.)
Solution/workaround:你可以用std::make_tuple
代替:
int greatestCommonDivisor(int a, int b)
{
if (b > a)
std::tie(a, b) = std::make_tuple(b, a);
while (b > 0)
std::tie(a, b) = std::make_tuple(b, a % b);
return a;
}
...或 C++17 中 std::tuple
的构造函数 (感谢 Template argument deduction for class templates):
int greatestCommonDivisor(int a, int b)
{
if (b > a)
std::tie(a, b) = std::tuple{b, a};
while (b > 0)
std::tie(a, b) = std::tuple{b, a % b};
return a;
}
为什么
std::tie(a,b) = {b, a};
无法编译?
赋值右侧的 {}
只能调用 operator=
.
参数的非显式构造函数
可用的 operator=
重载是:
tuple& operator=( const tuple& other );
tuple& operator=( tuple&& other );
template< class... UTypes >
tuple& operator=( const tuple<UTypes...>& other );
template< class... UTypes >
tuple& operator=( tuple<UTypes...>&& other );
template< class U1, class U2 >
tuple& operator=( const pair<U1,U2>& p );
template< class U1, class U2 >
tuple& operator=( pair<U1,U2>&& p );
template
运算符重载无法从 {}
推导出它们的类型(注意:这可能会在 C++17 中改变),留下:
tuple& operator=( const tuple& other );
tuple& operator=( tuple&& other );
在这种情况下,tuple
是 std::tuple<int&, int&>
。
tuple<Ts...>
的元组构造函数完美地转发了元素构造是 explicit (该列表中的第 3 个)。 {}
不会调用显式构造函数。
有条件的非显式构造函数采用Ts const&...
;如果 Ts
不可复制,并且 int&
不可复制,则不存在。
所以没有可行的类型可以从 {int&, int&}
构造,并且重载解析失败。
为什么标准没有解决这个问题?好吧,我们自己做吧!
为了解决这个问题,我们必须向 tuple
添加一个特殊的 (Ts...)
非显式构造函数,它仅在 Ts
类型都是引用时才存在。
如果我们写一个玩具元组:
struct toy {
std::tuple<int&, int&> data;
toy( int& a, int& b ):data(a,b) {} // note, non-explicit!
};
toy toy_tie( int& a, int& b ) { return {a,b}; }
并使用它,您会注意到
std::tie(a, b) = {b, a};
编译并 运行s.
然而,
std::tie(a, b) = { b, a % b };
不会,因为 a%b
不能绑定到 int&
。
然后我们可以通过以下方式扩充 toy
:
template<class...>
toy& operator=( std::tuple<int, int> o ) {
data = o;
return *this;
}
(+ 默认的特殊成员函数。template<class...>
确保它的优先级低于特殊成员函数,因为它应该如此。
这允许从 {int,int}
赋值。然后我们 运行 它并且...得到错误的结果。 5,20
的gcd是20
。出了什么问题?
toy_tie(a, b) = std::tie( b, a );
a
和 b
都绑定到引用不是安全代码,这就是
toy_tie(a, b) = { b, a };
会。
简而言之,要做到这一点很棘手。在这种情况下,您需要在分配之前对右侧进行 copy 以确保安全。知道什么时候复制什么时候不复制也很棘手。
隐含地进行这项工作看起来容易出错。所以,从某种意义上说,它不起作用是偶然的,但修复它(虽然可能)看起来是个坏主意。
我想知道为什么会做出这样的选择。它将允许以非常清晰和整洁的方式编写许多功能.. 例如:
int greatestCommonDivisor(int a, int b)
{
if (b > a)
std::tie(a, b) = { b, a };
while (b > 0)
std::tie(a, b) = { b, a % b };
return a;
}
std::initializer_list
is a homogeneous collection of items, while std::tuple
is heterogeneous. The only case where it makes sense to define a std::tuple::operator=
for std::initializer_list
是当元组是同质的并且与初始化列表具有相同大小时,这种情况很少见。
(Additional information in this question.)
Solution/workaround:你可以用std::make_tuple
代替:
int greatestCommonDivisor(int a, int b)
{
if (b > a)
std::tie(a, b) = std::make_tuple(b, a);
while (b > 0)
std::tie(a, b) = std::make_tuple(b, a % b);
return a;
}
...或 C++17 中 std::tuple
的构造函数 (感谢 Template argument deduction for class templates):
int greatestCommonDivisor(int a, int b)
{
if (b > a)
std::tie(a, b) = std::tuple{b, a};
while (b > 0)
std::tie(a, b) = std::tuple{b, a % b};
return a;
}
为什么
std::tie(a,b) = {b, a};
无法编译?
赋值右侧的{}
只能调用 operator=
.
可用的 operator=
重载是:
tuple& operator=( const tuple& other );
tuple& operator=( tuple&& other );
template< class... UTypes >
tuple& operator=( const tuple<UTypes...>& other );
template< class... UTypes >
tuple& operator=( tuple<UTypes...>&& other );
template< class U1, class U2 >
tuple& operator=( const pair<U1,U2>& p );
template< class U1, class U2 >
tuple& operator=( pair<U1,U2>&& p );
template
运算符重载无法从 {}
推导出它们的类型(注意:这可能会在 C++17 中改变),留下:
tuple& operator=( const tuple& other );
tuple& operator=( tuple&& other );
在这种情况下,tuple
是 std::tuple<int&, int&>
。
tuple<Ts...>
的元组构造函数完美地转发了元素构造是 explicit (该列表中的第 3 个)。 {}
不会调用显式构造函数。
有条件的非显式构造函数采用Ts const&...
;如果 Ts
不可复制,并且 int&
不可复制,则不存在。
所以没有可行的类型可以从 {int&, int&}
构造,并且重载解析失败。
为什么标准没有解决这个问题?好吧,我们自己做吧!
为了解决这个问题,我们必须向 tuple
添加一个特殊的 (Ts...)
非显式构造函数,它仅在 Ts
类型都是引用时才存在。
如果我们写一个玩具元组:
struct toy {
std::tuple<int&, int&> data;
toy( int& a, int& b ):data(a,b) {} // note, non-explicit!
};
toy toy_tie( int& a, int& b ) { return {a,b}; }
并使用它,您会注意到
std::tie(a, b) = {b, a};
编译并 运行s.
然而,
std::tie(a, b) = { b, a % b };
不会,因为 a%b
不能绑定到 int&
。
然后我们可以通过以下方式扩充 toy
:
template<class...>
toy& operator=( std::tuple<int, int> o ) {
data = o;
return *this;
}
(+ 默认的特殊成员函数。template<class...>
确保它的优先级低于特殊成员函数,因为它应该如此。
这允许从 {int,int}
赋值。然后我们 运行 它并且...得到错误的结果。 5,20
的gcd是20
。出了什么问题?
toy_tie(a, b) = std::tie( b, a );
a
和 b
都绑定到引用不是安全代码,这就是
toy_tie(a, b) = { b, a };
会。
简而言之,要做到这一点很棘手。在这种情况下,您需要在分配之前对右侧进行 copy 以确保安全。知道什么时候复制什么时候不复制也很棘手。
隐含地进行这项工作看起来容易出错。所以,从某种意义上说,它不起作用是偶然的,但修复它(虽然可能)看起来是个坏主意。