2

给定以下代码片段:

class Foo {};
Foo makeFoo() { return Foo{}; }

int main()
{
  Foo myFoo{makeFoo()};
}

我希望单行main声明和定义/初始化myFoo使用Foo的移动构造函数的返回值makeFoo()

clang++但是,我从3.5.1(以 C++14 模式编译)收到以下错误:

error: excess elements in struct initializer
      Foo myFoo{makeFoo()};
                ^~~~~~~~~
1 error generated.

这里发生了什么?“结构初始化程序”到底是什么意思——它只是 POD 的默认(无参数)构造函数吗?为什么不调用移动构造函数?

4

2 回答 2

7

毕竟,没有“通用(或统一)初始化语法”之类的东西。列表初始化有一些特殊的行为。

在您的情况下,相关规则可在第 8.5.1 节中找到:

聚合是一个数组或一个类(第 9 条),没有用户提供的构造函数(12.1),没有私有或受保护的非静态数据成员(第 11 条),没有基类(第 10 条),也没有虚函数(10.3) )。

因此,您class Foo是一个聚合

当聚合由初始化列表初始化时,如 8.5.4 中所指定,初始化列表的元素被视为聚合成员的初始化,按递增的下标或成员顺序。每个成员都是从相应的初始化子句复制初始化的。如果 initializer-clause 是一个表达式并且需要一个窄化转换 (8.5.4) 来转换该表达式,则程序是非良构的。

这就是编译器解释您的代码的方式(正如@chris 所指出的,在下一版本的 C++ 中,它不会这样做......虽然我认为这个规则也需要更新,只是“如在8.5.4" 并不足以停止聚合初始化行为)。

由于初始化器的数量多于成员,因此这是非法的。

作为一个类的聚合也可以用一个不包含在大括号中的表达式来初始化,如 8.5 中所述。

这是允许复制/移动初始化的规则。聚合的复制/移动不能使用大括号。

于 2015-04-10T22:44:11.700 回答
4

因为Foo, 是一个聚合,所以执行聚合初始化。

N3797 §8.5.4 [dcl.init.list]/3:

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

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

根据 N4296,C++17 似乎已经改变了这一点:

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

  • 如果 T 是一个类类型并且初始化器列表有一个类型为 cv U 的元素,其中 U 是 T 或从 T 派生的类,则从该元素初始化对象(通过复制列表初始化的复制初始化,或通过直接列表初始化的直接初始化)。
于 2015-04-10T22:42:46.837 回答