使用统一初始化构造对象的简单程序编译失败

Simple program using uniform initialization to construct an object fails to compile

本文关键字:简单 程序 编译 失败 对象 初始化      更新时间:2023-10-16

考虑以下程序:

struct X
{
    X(int, int) { }
    X(X&&) { }
};
int main()
{
    X x( {0, 1} ); // Doesn't compile on ICC 13.0.1, compiles on
                   // Clang 3.2, GCC 4.7.2, and GCC 4.8.0 beta.
}

当使用GCC 4.7.2, GCC 4.8.0和Clang 3.2编译时,该程序执行以下操作(*):

  1. 构造一个X类型的临时变量,将值01传递给构造函数,则;
  2. 移动X从临时构建。

对于ICC 13.0.1,它不编译。

问题1:谁是对的?

(*) 实际上,省略了临时变量的创建和对move构造函数的调用,但是使用-fno-elide-constructors选项编译并向构造函数添加一些打印输出显示了正在发生的事情。


现在考虑上面程序的以下细微变化,其中使用统一初始化直接初始化x:

int main()
{
    X x{ {0, 1} }; // ERROR! Doesn't compile.
//     ^........^
}

我不希望用大括号代替括号来改变这里的任何东西,但它确实改变了:这个程序不能在我测试过的任何编译器上编译(Clang 3.2, GCC 4.7.2, GCC 4.8.0 beta和ICC 13.0.1)。

问题2:为什么?

这只是所有编译器中的一个bug。§8.5.4/3说,

类型T的对象或引用的列表初始化定义如下:

—如果初始化列表没有元素,并且T是带有默认构造函数的类类型,则对象为初始化值。

—否则,如果T是聚合,则执行聚合初始化(8.5.1)。

-否则,如果T是std::initializer_list的专门化,则按如下所述构造initializer_list对象并用于初始化该对象…

—否则,如果T是类类型,则考虑构造函数。枚举适用的构造函数,并通过重载解析选择最佳构造函数(13.3,13.3.1.7)。如果需要窄化转换(见下文)来转换任何参数,则程序是病态的。

—否则,如果T是引用类型,则T引用的类型的右值临时值被列表初始化,并且引用被绑定到该临时类型。

—否则,如果初始化列表只有一个元素,则从该元素初始化对象或引用;如果需要缩窄转换(见下文)来将元素转换为T,则程序是病态的。

相当多的案例。请注意,您实际上是在列表初始化一个绑定到右值引用的右值临时对象。

对于GCC,我认为它正在尝试应用上面提到的最后一项,用于单元素初始化列表。如果我将构造函数签名更改为X(X&&, int = 3),则初始化器{ {0, 1} }失败,但{ {0, 1}, 3 }成功。单个项目应该成功,因为元素是一个大括号初始化列表,我相信case应该允许额外的大括号,类似于双亲。但是这种失败与使用大括号省略的GCC的其他缺点相似。

我的高级印象是,当编译器试图将列表视为具有类型的对象时,问题就出现了,而它不是。很难将其转换回圆括号式参数列表。

更具体地查看错误消息(感谢LWS链接),

  • ICC坚持需要一个表达式。这是错误的,因为根据基本语法,大括号初始化列表可以包含其他大括号初始化列表,而不仅仅是表达式。

  • Clang说"候选构造函数不可行:不能将初始化列表参数转换为'X'"但如果转换是显式的,使用X x{ X{0, 0 } };,它可以工作。这说不通啊。这不是转换,因为列表没有可转换的类型。list-initialization。

  • GCC说"没有已知的参数1从"到'X&&'的转换"表明它甚至没有定义一个临时变量来绑定到引用。与Clang一样,它似乎在尝试虚假的转换,并指定X{0,0}修复它。