为什么不能为 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 );

在这种情况下,tuplestd::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 );

ab 都绑定到引用不是安全代码,这就是

  toy_tie(a, b) = { b, a };

会。

简而言之,要做到这一点很棘手。在这种情况下,您需要在分配之前对右侧进行 copy 以确保安全。知道什么时候复制什么时候不复制也很棘手。

隐含地进行这项工作看起来容易出错。所以,从某种意义上说,它不起作用是偶然的,但修复它(虽然可能)看起来是个坏主意。

live example.