在指定初始化程序中使用较早的成员

Gug*_*ugi 10 c++ designated-initializer language-lawyer aggregate-initialization c++20

考虑以下代码:

struct Foo{
    std::string s1;
    std::string s2;
};

int main(){
    Foo f{.s1 = "s1", .s2 = f.s1 + "s2"};
    std::cout << "s1='" << f.s1 << "', s2='" << f.s2 << "'" << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

特别注意 的初始化访问:s2的第一个成员。最新的 clang、gcc 和 MSVC 对代码很满意,并给出了天真的预期结果(它们打印)。观看Godbolt直播。f.s2 = f.s1 + "s2""s1='s1', s2='s1s2'"

问题:这合法吗?换句话说,标准是否保证在f.s1指定的初始化器被.s2评估之前被初始化?

相关:有一个类似的问题询问是否.s2 = .s1 + "s2"合法,这显然是不合法的,因为它无法编译。另外,P0328(根据答案)可能相关,但我看不到我的问题在那里得到解答。

Far*_*nor 13

在 C++ 中,顺序很重要。成员变量按照声明的顺序进行初始化。

\n

初始化列表,无论是否指定,都遵循此规则。

\n

在您的情况下,s1是在 之前声明s2Foo。因此,它将首先被初始化。

\n

如果您做了相反的事情(即s1基于定义s2),那么它将是格式 正确的未定义行为,因为您将使用尚未初始化的成员。

\n
\n

9.4.5 列表初始化 (\xc2\xa73.1) 开始

\n
\n

如果花括号初始化列表包含指定初始化列表并且 T 不是引用类型,则 T 应是聚合类。指定初始化列表的指示符中的有序标识符应形成 T 的直接非静态数据成员中的有序标识符的子序列。执行聚合初始化([dcl.init.aggr])。

\n
\n

然后从9.4.2 聚合 (\xc2\xa77)开始:

\n
\n

聚合元素的初始化按元素顺序进行评估。也就是说,与给定元素相关的所有值计算和副作用都按顺序排列在其后面的任何元素的计算和副作用之前。

\n
\n


Lun*_*din 5

简化答案:

\n

没问题,只要遵循从左到右的顺序即可。

\n

您可能认为初始值设定项列表遵循与运算符优先级和运算符评估相同的规则。在这种情况下,,初始化列表中的 类似于具有显式从左到右计算的逗号运算符。

\n

类似地,初始化 with=在很大程度上类似于赋值运算符。同样的转换也会发生。(除非我们使用构造函数或重载赋值运算符创建专门的用途。)

\n

语言律师解答:

\n

这是明确定义的,初始化器列表中的项目按从左到右的顺序进行初始化/评估/排序。

\n

A struct/class是一个“聚合”,C++20 9.4.1 表示:

\n
\n

当聚合由 9.4.4 中指定的初始值设定项列表初始化时,初始值设定项列表的元素将被视为聚合元素的初始值设定项。

\n
\n

深入研究 C++20 9.4.4 \xc2\xa74:

\n
\n

花括号初始化列表初始化列表中,初始化子句(包括任何由包扩展(13.7.3)产生的子句)按照它们出现的顺序进行计算。也就是说,与给定初始值设定项子句关联的每个值计算和\n副作用都在与初始值设定项列表的逗号分隔列表中跟随它的任何初始值设定项子句关联的每个值计算和副作用之前排序。

\n
\n