花括号初始化列表和赋值操作

6
#include <iostream>

struct int_wrapper
{
    int i;

    int_wrapper(int i = 0) : i(i) {}
};

struct A
{
    int_wrapper i;
    int_wrapper j;

    A(int_wrapper i = 0, int_wrapper j = 0) : i(i), j(j) {}
};

int main()
{
    A a;

    a = {3};

    // error: no match for ‘operator=’ (operand types are ‘A’ and ‘int’)
    // a = 3;

    std::cout << a.i.i << std::endl;

    return 0;
}

我知道在进行隐式转换时不允许多个用户转换,但是,为什么使用花括号初始化列表可以进行双重用户转换呢?

1个回答

7
记住:使用花括号初始化列表意味着“初始化一个对象”。你得到的是一个隐式转换,然后是对象初始化。由于这就是你所要求的,因此你得到了“双重用户转换”。
当执行 a = <something>; 时,相当于执行 a.operator=(<something>)
当 <something> 是一个花括号初始化列表时,这意味着它将执行 a.operator=({3})。这将基于从花括号初始化列表中初始化其第一个参数来选择 operator= 重载。将调用的重载函数将是可以通过花括号初始化列表中的值来初始化其第一个参数的类型。
该运算符有两个重载形式,即复制赋值和移动赋值。由于这个花括号初始化列表初始化了一个 prvalue,所以调用的首选方法将是移动赋值运算符(虽然这并不重要,因为它最终导致相同的结果)。移动赋值的参数是 A&&,因此花括号初始化列表将尝试初始化一个 A。它将通过复制列表初始化规则来执行这样的操作。
在选择要调用的 operator= 函数之后,现在我们初始化 A。由于 A 的构造函数不是 explicit 的,因此复制列表初始化可以调用它。由于该构造函数有两个默认参数,因此只能使用单个参数来调用它。而在 A 构造函数的第一个参数的类型 int_wrapper 是从花括号初始化列表中第一个值的类型 int 隐式转换而来。
因此,你得到了一个隐式转换成 int_wrapper 的结果,这将被复制列表初始化用于初始化一个 prvalue 临时对象,然后通过移动赋值运算符分配给类型为 A 的现有对象。
相比之下,a.operator=(3) 尝试直接将 3 隐式转换为 A,这需要两个转换步骤,因此不允许。
只需记住,花括号初始化列表意味着“初始化一个对象”。

你让我有点困惑。你说“一个大括号初始化列表初始化了 prvalue”,然后又说“大括号初始化列表意味着‘初始化对象’”。据我所知,大括号初始化列表是初始化对象的一种统一方式。关于 prvalue 部分(我知道 prvalue 是什么),可能需要进一步澄清。 - KeyC0de
1
@Nik-Lz:编辑:“这个花括号初始化列表初始化了一个prvalue”。由于重载选择选择了一个接受引用的函数,它必须创建一个prvalue临时对象并将引用绑定到它上面。 - Nicol Bolas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接