通过从函数返回值移动进行大括号初始化会产生"excess elements"错误

Brace-initialization via move from function return value gives "excess elements" error

本文关键字:初始化 elements excess 错误 函数 返回值 移动      更新时间:2023-10-16

给定以下代码片段:

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

我希望main中的单行使用FoomakeFoo()的返回值的move构造函数来声明和定义/初始化myFoo

然而,我从clang++3.5.1(在C++14模式下编译)中得到以下错误:

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

这是怎么回事?"结构初始化器"到底是什么意思——它只是POD的默认(无参数)构造函数吗?为什么没有调用move构造函数?

毕竟没有"通用(或统一)初始化语法"这回事。列表初始化有一些特殊的行为。

在您的情况下,相关规则见第8.5.1节:

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

因此,您的class Foo聚合

当聚合由初始化器列表初始化时,如8.5.4中所述,初始化器列表的元素被视为聚合成员的初始化器,按下标或成员顺序递增。每个成员是从相应的初始值设定项子句复制初始化的。如果初始值设定项子句是一个表达式,并且需要进行收缩转换(8.5.4)来转换该表达式,则程序格式不正确。

这就是编译器解释代码的方式(正如@chris所指出的,在C++的下一个版本中,它不会这么做……尽管我认为这个规则也需要更新,但仅仅"按照8.5.4中的规定"并不足以停止聚合初始化行为)。

由于初始值设定项多于成员,因此这是非法的。

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

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

由于Foo是一个聚合,因此执行聚合初始化。

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

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

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

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

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

  • 如果T是类类型,并且初始值设定项列表具有类型cv U的单个元素,其中U是T或从T派生的类,对象从该元素初始化(通过复制初始化用于复制列表初始化,或通过直接初始化直接列表初始化)