【问题标题】:Direct initialization with empty initializer list使用空初始化器列表直接初始化
【发布时间】:2018-07-15 00:30:27
【问题描述】:
struct X
{
    X() { std::cout << "default ctor" << std::endl; }

};

int main()
{
    X({});
}

打印出来

default ctor

这是有道理的,因为空括号值初始化对象(我认为)。 然而,

struct X
{
    X() { std::cout << "default ctor" << std::endl; }
    X(std::initializer_list<int>) { std::cout << "initializer list" << std::endl; }
};

int main()
{
    X({});
}

为此,我得到了

initializer list

我不觉得这种行为很奇怪,但我并不完全相信。这是什么规则?

此行为是否写入标准的某些部分?

【问题讨论】:

标签: c++ c++11 language-lawyer initializer-list


【解决方案1】:

要查看实际情况,请声明复制和移动构造函数,在 C++14 或更早的模式下编译,并禁用复制省略。

Coliru link

输出:

default ctor
move ctor

在第一个 sn-p 中,编译器会查找带有单个参数的 X 的构造函数,因为您提供了单个参数。这些是复制和移动构造函数X::X(const X&amp;)X::X(X&amp;&amp;),如果您自己不声明它们,编译器将为您隐式声明它们。然后编译器使用默认构造函数将{} 转换为X 对象,并将该X 对象传递给移动构造函数。 (您必须使用fno-elide-constructors 才能看到这一点,否则编译器将忽略移动,并且在 C++17 中复制省略成为强制性的。)

在第二个 sn-p 中,编译器现在可以选择将{} 转换为X(然后调用移动构造函数),或将{} 转换为std::initializer_list&lt;int&gt;(然后调用初始化列表构造函数) .根据[over.ics.list]/6.2,{}X的转换调用默认构造函数,是自定义转换,而根据[over.ics.list]/4,从{}std::initializer_list&lt;int&gt; 的转换是身份转换。标识转换比用户定义的转换要好,所以编译器调用初始化列表构造函数。

【讨论】:

  • 感谢您的具体示例和解释。这让我更容易理解。
【解决方案2】:

此行为是否写入标准的某些部分?

当然。这一切都由[dcl.init]/16 中的规则规定,强调我的以匹配您的初始化程序:

初始化器的语义如下。目的地类型是 正在初始化的对象或引用的类型和源 type 是初始化表达式的类型。如果初始化器是 不是单个(可能是括号)表达式,源类型是 未定义。

  • 如果初始化器是(非括号)大括号初始化列表,则对象或引用是列表初始化的([dcl.init.list])。

  • [...]

  • 如果目标类型是(可能是 cv 限定的)类类型:

    • 如果初始化是直接初始化,或者如果是复制初始化,其中源的 cv-unqualified 版本 type 与该类的类相同或派生类 目的地,考虑构造函数。适用的构造函数 被枚举([over.match.ctor]),并通过 重载决议([over.match])。如此选择的构造函数是 调用以初始化对象,使用初始化表达式或 表达式列表作为其参数。如果没有构造函数适用,或者 重载决议不明确,初始化格式不正确。
    • [...]

您提供了一个带括号的空括号初始化列表,因此只有后面的项目符号适用。考虑了构造函数,在第一种情况下,我们最终从默认初始化的X 进行复制初始化。在后一种情况下,initializer_list c'tor 被选为更好的匹配。选择这个重载的规则在[over.ics.list]中指定:

当参数是初始化列表 ([dcl.init.list]) 时,它不是 表达式和特殊规则适用于将其转换为参数 输入。

如果参数类型是 std::initializer_list 或“X 数组”并且 初始化器列表的所有元素都可以隐式转换 到 X,隐式转换序列是最差的转换 将列表的元素转换为 X 所必需的。这种转换可以 即使在调用 初始化列表构造函数。

否则,如果参数是非聚合类 X 并且重载 每个 [over.match.list] 的分辨率选择一个最佳构造函数 X 执行 X 类型对象的初始化 参数初始化器列表,隐式转换序列是 用户定义的转换序列。如果多个构造函数是可行的 但没有一个比其他的更好,隐式转换序列 是模棱两可的转换序列。用户定义的转换是 允许将初始化列表元素转换为 构造函数参数类型,除非在 [over.best.ics] 中注明。

【讨论】:

  • 所以基本上选择了最适合的构造函数。对吧?
  • @GauravSehgal - 差不多。 [over.ics.list] 详细说明如何选择转换顺序。
  • 哇,你真的是一名语言律师。谢谢你的大力帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-02-08
  • 1970-01-01
  • 1970-01-01
  • 2018-10-29
  • 2011-06-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多