Brace-init-list 和赋值

Brace-init-list and assignments

#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;
}

我知道在进行隐式转换时不允许超过一次用户转换,但是,为什么使用 brace-init-list 双重用户转换有效?

记住:使用花括号初始化列表意味着 "initialize an object"。您得到的是隐式转换,然后是对象初始化。您得到 "double user-conversion" 因为那是您 要求的

当您执行 a = <something>; 时,它的作用相当于 a.operator=(<something>)

当那个东西是一个 braced-init-list 时,这意味着它会做 a.operator=({3})。这将选择一个 operator= 重载,基于从 braced-init-list 初始化他们的第一个参数。将被调用的重载将采用可以由花括号初始化列表中的值初始化的类型。

该运算符有两个重载。即,复制分配和移动分配。因为这个花括号初始化列表初始化了一个纯右值,所以调用的首选函数将是移动赋值运算符(这并不重要,因为它都导致相同的事情)。 move-assignment 的参数是 A&&,因此 braced-init-list 将尝试初始化一个 A。它将通过复制列表初始化规则来实现。

选择要调用的 operator= 函数后,我们现在初始化 A。由于A的构造函数不是explicit,copy-list-initialization可以自由调用。由于该构造函数有 2 个默认参数,因此可以仅使用一个参数调用它。 A 的构造函数中的第一个参数的类型 int_wrapper 可以从花括号初始化列表中的第一个值的类型 int.[=27 隐式转换=]

因此,您得到了到 int_wrapper 的隐式转换,复制列表初始化使用它来初始化纯右值临时对象,该临时对象用于分配给 [=15= 类型的现有对象] 通过移动赋值。

相比之下,a.operator=(3) 试图直接从 3 隐式转换为 A。这当然需要两个转换步骤,因此是不允许的。

请记住,braced-init-lists 意味着 "initialize an object"。