6

考虑以下程序:

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:谁是对的?

(*)实际上,临时的创建和对移动构造函数的调用被省略了,但是使用-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 测试版和 ICC 13.0.1)。

问题2:为什么?

4

1 回答 1

2

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

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

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

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

— 否则,如果 T 是 std::initializer_list 的特化,则按如下所述构造一个 initializer_list 对象并用于初始化该对象……</p>

— 否则,如果 T 是类类型,则考虑构造函数。枚举适用的构造函数,并通过重载决议(13.3、13.3.1.7)选择最佳构造函数。如果需要缩小转换(见下文)来转换任何参数,则程序格式错误。

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

— 否则,如果初始化列表只有一个元素,则从该元素初始化对象或引用;如果需要缩小转换(见下文)将元素转换为 T,则程序格式错误。

…</p>

不少案例。请注意,您实际上是在列表初始化绑定到右值引用的纯右值临时对象。

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

我的高级印象是,当编译器尝试将列表视为具有类型的对象时会出现问题,但事实并非如此。很难将其转换回带括号的参数列表之类的东西。

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

  • ICC 坚称它期待表达。这是错误的,因为根据基本语法,花括号初始化列表可以包含其他花括号初始化列表,而不仅仅是表达式。

  • Clang 说“候选构造函数不可行:无法将初始值设定项列表参数转换为 'X'”,但如果转换是显式的,则它可以工作,使用X x{ X{0, 0 } };. 那没有意义。这不是转换,因为列表没有要转换的类型。这是列表初始化。

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

于 2013-02-28T03:00:13.610 回答