【问题标题】:Why does Foo({}) invoke Foo(0) instead of Foo()?为什么 Foo({}) 调用 Foo(0) 而不是 Foo()?
【发布时间】:2014-11-06 20:02:47
【问题描述】:

clang 3.5.0 和 gcc 4.9.1 从代码生成的可执行文件

#include <iostream>

struct Foo
{
   Foo() { std::cout << "Foo()" << std::endl; }
   Foo(int x) { std::cout << "Foo(int = " << x << ")" << std::endl; }
   Foo(int x, int y) { std::cout << "Foo(int = " << x << ", int = " << y << ")" << std::endl; }
};

int main()                 // Output
{                          // ---------------------
   auto a = Foo();         // Foo()
   auto b = Foo(1);        // Foo(int = 1)
   auto c = Foo(2, 3);     // Foo(int = 2, int = 3)
   auto d = Foo{};         // Foo()
   auto e = Foo{1};        // Foo(int = 1)
   auto f = Foo{2, 3};     // Foo(int = 2, int = 3)
   auto g = Foo({});       // Foo(int = 0)          <<< Why?
   auto h = Foo({1});      // Foo(int = 1)
   auto i = Foo({2, 3});   // Foo(int = 2, int = 3)
}

按照评论行事。

来自cppreference: cpp/language/list initialization

[...]

T( { arg1, arg2, ... } )    (7)

[...]

T 类型对象的列表初始化的效果是:

如果T 是聚合类型,则执行聚合初始化。

否则,如果braced-init-list为空且T是具有默认构造函数的类类型,则执行值初始化。

[...]

我的结论是Foo({}) 应该调用默认构造函数。

错误在哪里?

【问题讨论】:

  • 错误在 cppreference 中。
  • 修复了 cppreference 以读取“从 非括号括号初始化列表中类型为 T 的对象的列表初始化效果是...”更清楚了吗?
  • 也许将T({...}) 替换为U({...}) 可能是一个好主意,以避免引用T 的文本出现问题,这里不正确。
  • @precarious 它仍然是精确的。如果删除了 int 构造函数,您将调用复制/移动构造函数。

标签: c++ language-lawyer c++14 list-initialization value-initialization


【解决方案1】:

默认构造函数仅在您使用一对大括号时适用:

auto a = Foo();         // Foo()
auto b = Foo{};         // Foo()

Foo({}) 只会调用以空列表作为参数的构造函数,复制列表初始化所选构造函数的参数。 [dcl.init]/16:

如果目标类型是(可能是 cv 限定的)类类型:
— 如果 初始化是直接初始化 […] 考虑构造函数。适用的构造函数 枚举(13.3.1.3),通过重载选择最好的 决议(13.3)。 如此选择的构造函数被调用到 使用初始化表达式或初始化对象 表达式列表 作为它的参数。 如果没有构造函数适用,或者 重载决议不明确,初始化是 格式不正确。

你有一个论点:空的括号初始化列表。有一个列表初始化序列将{} 转换为int,因此构造函数Foo(int) 由重载决议选择。该参数被初始化为零,因为{} 意味着value-intialization,对于标量,意味着zero-initialization

cppreferences 文档中也没有错误:对于 (7),声明

7) 在函数转换表达式或其他直接初始化中, 使用花括号初始化列表作为构造函数参数

这显然会导致与上面引用相同的结果:构造函数是用(空的)braced-init-list 调用的。

【讨论】:

  • +1 为了澄清 OP,本例中的代码等效于 auto g = Foo(int{});int{} == 0
  • 谈到非标准方法,在Visual Studio / msvc 中,当“列表”包含 1 个可以解释为整数类型的文字时,例如{42},这不是一个列表——因为它应该根据标准——但就编译器 - msvc - 而言,它是一个“真正的”整数;例如auto a = {1}, a 如果你使用msvc,它是一个整数。
  • @Hurkyl 在这种特殊情况下的预期结果是一个错误,因为{} 并没有真正的类型,因此根据标准编译器应该将auto 视为类型请求推论,a 作为标签,= 作为分配/复制 ctor,{1} 作为没有类型的东西,其中包含文字;因此编译器不应该能够为a 推断出任何类型,这就是为什么它应该生成错误而不是整数类型的实例。微软将此例外添加到规则中,我认为此例外不会更好地使用{}
  • @user2485710 初始化器列表没有类型,因为它们不是表达式。但 auto a = {1} 格式正确,不应产生错误。该类型应推断为 std::initializer_list (这是一个备受争议的特殊情况)。如果微软推导出int,那就是错误。但您不必期待错误。
  • Post-N3922auto a = {1}; 应该推导出一个std::initializer_listauto a{1}; 应该推导出一个int,并且auto a{1,2}; 应该是格式错误的。
猜你喜欢
  • 2020-10-03
  • 1970-01-01
  • 1970-01-01
  • 2023-03-11
  • 2010-12-12
  • 2017-05-15
  • 2017-12-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多