为什么std :: tuple不能分配初始化列表?

mr_*_*r_T 5 c++11 stdtuple

我想知道为什么要做出这个选择.它将允许以非常清晰和整洁的方式编写许多函数..例如:

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;
}
Run Code Online (Sandbox Code Playgroud)

Vit*_*meo 6

std::initializer_list是一个同类的项目集合,std::tuple而是异构的.唯一有意义定义std::tuple::operator=for的情况std::initializer_list是元组是同构的并且与初始化列表具有相同的大小,这种情况很少发生.

(此问题中的其他信息.)


解决方案/解决方法:您可以使用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;
}
Run Code Online (Sandbox Code Playgroud)

...或者std::tupleC++ 17中的构造函数(感谢类模板的模板参数推导):

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;
}
Run Code Online (Sandbox Code Playgroud)


Yak*_*ont 4

为什么

std::tie(a,b) = {b, a};
Run Code Online (Sandbox Code Playgroud)

不编译?

{}赋值右侧只能调用 参数的非显式构造函数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 );
Run Code Online (Sandbox Code Playgroud)

运算template符重载无法从中推断出它们的类型{}(注意:这在 C++17 中可能会改变),留下:

tuple& operator=( const tuple& other );
tuple& operator=( tuple&& other );
Run Code Online (Sandbox Code Playgroud)

tuple在这种情况下在哪里std::tuple<int&, int&>

完美的前向逐元素构造的元组构造函数tuple<Ts...>显式的(该列表中的#3)。 {}不会调用显式构造函数。

有条件的非显式构造函数采用Ts const&...; Ts如果不可复制,则它不存在,并且int&不可复制。

因此,没有可用的类型可以构造{int&, int&},并且重载解析失败。


为什么标准没有解决这个问题?好吧,我们可以自己做!

为了解决这个问题,我们必须添加一个特殊的(Ts...)非显式构造函数,tuple该构造函数仅在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}; }
Run Code Online (Sandbox Code Playgroud)

并使用它,你会注意到

    std::tie(a, b) = {b, a};
Run Code Online (Sandbox Code Playgroud)

编译并运行。

然而,

    std::tie(a, b) = { b, a % b };
Run Code Online (Sandbox Code Playgroud)

不,因为a%b不能绑定到int&

然后我们可以增加toy

template<class...>
toy& operator=( std::tuple<int, int> o ) {
    data = o;
    return *this;
}
Run Code Online (Sandbox Code Playgroud)

(+默认的特殊成员函数。 template<class...>确保它的优先级低于特殊成员函数,因为它应该)。

这让 allocate-from {int,int}。然后我们运行它并......得到错误的结果。的 gcd5,2020. 什么地方出了错?

  toy_tie(a, b) = std::tie( b, a );
Run Code Online (Sandbox Code Playgroud)

同时使用ab绑定到引用不是安全的代码,这就是

  toy_tie(a, b) = { b, a };
Run Code Online (Sandbox Code Playgroud)

做。

简而言之,正确地做到这一点是很棘手的。在这种情况下,为了安全起见,您需要在分配之前获取右侧的副本。知道何时复制、何时不复制也很棘手。

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

活生生的例子