复制形式'= {}'的初始化

Ric*_*ich 14 c++ copy-initialization c++11 list-initialization c++14

鉴于以下内容:

#include <stdio.h>

class X;

class Y
{
public:
  Y() { printf("  1\n"); }             // 1
  // operator X(); // 2
};

class X
{
public:
  X(int) {}
  X(const Y& rhs) { printf("  3\n"); } // 3
  X(Y&& rhs) { printf("  4\n"); }      // 4
};

// Y::operator X() { printf("   operator X() - 2\n"); return X{2}; }

int main()
{
  Y y{};     // Calls (1)

  printf("j\n");
  X j{y};    // Calls (3)
  printf("k\n");
  X k = {y}; // Calls (3)
  printf("m\n");
  X m = y;   // Calls (3)
  printf("n\n");
  X n(y);    // Calls (3)

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.现在,如果我启用转换运算符Y::operator X(),我会得到这个; -

  X m = y; // Calls (2)
Run Code Online (Sandbox Code Playgroud)

我的理解是发生这种情况是因为(2)比(3)"更少const",因此是首选.对X构造函数的调用被省略了

我的问题是,为什么定义不会X k = {y}以同样的方式改变其行为?我知道这= {}在技​​术上是"列表复制初始化",但是在没有构造函数采用initializer_list类型的情况下,这不会恢复为"复制初始化"行为吗?即 - 与...相同X m = y

我的理解在哪里?

Bar*_*rry 7

我的理解在哪里?

tltldr; 没有人理解初始化.

tldr; 列表初始化更喜欢std::initializer_list<T>构造函数,但它不会回退到非列表初始化.它只会回到考虑构造函数.非列表初始化将考虑转换函数,但回退不会.


所有初始化规则都来自[dcl.init].所以,让我们从第一原则出发.

[dcl.init] /17.1:

  • 如果初始化程序是(非括号)braced-init-list或is = braced-init-list,则对象或引用是列表初始化的.

第一个第一个要点包括任何列表初始化.这种跳跃X x{y}X x = {y}[dcl.init.list] .我们会回过头来看看.另一种情况比较容易.我们来看看吧X x = y.我们直接打电话给:

[dcl.init] /17.6.3:

  • 否则(即,对于剩余的复制初始化情况),用户定义的转换序列可以从源类型转换为目标类型或(当使用转换函数时)到其派生类,如[over]中所述进行枚举.match.copy],通过重载决策选择最好的一个.match.copy].

[over.match.copy]中的候选人是:

  • T[在我们的例子中X] 的转换构造函数是候选函数.
  • 当初始化表达式的类型是类类型" cv S "时,将S考虑其非基本类的非显式转换函数.

在这两种情况下,参数列表都有一个参数,它是初始化表达式.

这给了我们候选人:

X(Y const &);     // from the 1st bullet
Y::operator X();  // from the 2nd bullet
Run Code Online (Sandbox Code Playgroud)

第二个相当于有一个X(Y& ),因为转换函数不是cv合格的.这使得比转换构造函数更少的cv限定引用,因此它是首选.注意,X(X&& )在C++ 17中没有调用.


现在让我们回到列表初始化案例.第一个相关的要点是[dcl.init.list] /3.6:

否则,如果T是类类型,则考虑构造函数.枚举适用的构造函数,并通过重载决策([over.match],[over.match.list])选择最佳构造函数.如果转换任何参数需要缩小转换(见下文),则程序格式错误.

在两种情况下都将我们带到[over.match.list],它定义了两阶段重载决策:

  • 最初,候选函数是类T的初始化列表构造函数([dcl.init.list]),参数列表由初始化列表作为单个参数组成.
  • 如果找不到可行的初始化列表构造函数,则再次执行重载解析,其中候选函数是类T的所有构造函数,参数列表由初始化列表的元素组成.

如果初始化列表没有元素且T具有默认构造函数,则省略第一个阶段.在copy-list-initialization中,如果选择了显式构造函数,则初始化是错误的.

候选人是...的建设者X.X x{y}和之间的唯一区别X x = {y}是,如果后者选择explicit构造函数,则初始化是错误的.我们甚至没有任何explicit构造函数,因此两者是等价的.因此,我们枚举我们的构造函数:

  • X(Y const& )
  • X(X&& ) 通过 Y::operator X()

前者是直接引用绑定,是完全匹配.后者需要用户定义的转换.因此,X(Y const& )在这种情况下我们更喜欢.


请注意,gcc 7.1在C++ 1z模式下出错,所以我提交了错误80943.