我能否在初始化列表中引用先前的成员?

29

假设我想引用已定义的initializer_list的成员,我能做到吗?

在Visual Studio和gcc中,此代码编译并输出预期结果:"13 55 ",我只是想知道它是否合法:

const int foo[2] = {13, foo[0] + 42};

1
@NathanOliver 谢谢,我同意。但这是完全不同的问题。阅读关于结构体的页面来查找关于数组的答案并不是建设性的。 - Jonathan Mee
1
DR1343看起来似乎还不够;需要一个绝对的声明,即对于聚合初始化,在前一个元素的初始化完成之前,必须不要评估初始值。正如Shafik所说,目前似乎没有任何措辞防止列表的所有元素被评估,然后将结果应用于聚合体。 - M.M
3
这是一个大括号初始化列表,而不是initializer_list,对吗? - Baum mit Augen
3
哇哦...我真蠢。谢谢你提供链接。 - Jonathan Mee
1
@xskxzr 我同意它们很相似,但这绝对不是重复的,因为它与数组无关。 - Jonathan Mee
显示剩余10条评论
1个回答

23

这里涉及到C++标准草案中的第8.5.1节中介绍的聚合初始化:

聚合体是指数组或类[...]

以及:

当使用初始化器列表(如8.5.4所规定)对聚合体进行初始化时,初始化器列表中的元素将按照下标或成员顺序递增作为聚合体成员的初始化器。每个成员都从相应的初始化器子句中进行复制初始化[...]

尽管似乎合理的是,从初始化聚合体的每个成员中产生的副作用应该在下一个成员之前被排序,因为初始化列表中的每个元素都是完整的表达式。但标准实际上并没有保证这一点,我们可以从defect report 1343中看到这一点,其中指出:

当前措辞并未表明非类对象的初始化是完整表达式,但可能应该这样做。

同时也注意到:

聚合初始化也可以涉及多个完整表达式,因此上述对“非类对象的初始化”的限制是不正确的。另外,我们可以从相关的std-discussion topic中看到Richard Smith的说法:
[intro.execution]p10:“完整表达式是不是另一个表达式的子表达式的表达式。[...]如果语言结构被定义为产生函数的隐式调用,则对语言结构的使用被认为是此定义目的下的表达式。” 由于大括号初始化列表不是表达式,在这种情况下它不会导致函数调用,因此5和s.i是单独的完整表达式。然后: [intro.execution]p14:“与完整表达式相关的每个值计算和副作用都在评估下一个要评估的完整表达式之前排序。” 所以唯一的问题是,初始化s.i的副作用是否“与”评估完整表达式“5”相关联?我认为唯一合理的假设是它是:如果5正在初始化类类型的成员,则按照[intro.execution]p10中的定义,构造函数调用显然是完整表达式的一部分,因此可以自然地假设标量类型也是如此。 但是,我不认为标准在任何地方明确说明这一点。
目前标准中尚未对此进行规定,因此不能依赖它,不过如果实现没有按照您的期望处理,我会感到惊讶。对于这样简单的情况,以下类似内容似乎是更好的选择:
constexpr int value = 13 ;
const int foo[2] = {value, value+42};

C++17的变化

提案P0507R0:核心问题1343:非类初始化的顺序澄清了此处提出的完整表达点,但并未回答初始化的副作用是否包含在完整表达式的评估中的问题。因此,这仍然是未指定的。

这个问题的相关变化在[intro.execution]中:

A constituent expression is defined as follows:

(9.1) — The constituent expression of an expression is that expression.

(9.2) — The constituent expressions of a braced-init-list or of a (possibly parenthesized) expression-list are the constituent expressions of the elements of the respective list.

(9.3) — The constituent expressions of a brace-or-equal-initializer of the form = initializer-clause are the constituent expressions of the initializer-clause. [ Example:

struct A { int x; };
struct B { int y; struct A a; };
B b = { 5, { 1+1 } };

The constituent expressions of the initializer used for the initialization of b are 5 and 1+1. —end example ]

并且 [intro.execution]p12:

完整表达式是

(12.1) — 未求值的操作数 (第8条款),

(12.2) — 常量表达式 (8.20),

(12.3) — 初始化声明符 (第11条款) 或成员初始化器 (15.6.2),包括初始化器的组成表达式,

(12.4) — 在非临时对象的生命周期结束时生成的析构函数调用 (15.2), 或

(12.5) — 不是另一个表达式的子表达式且不属于完整表达式的其他部分的表达式。

在这种情况下,13foo[0] + 42都是一个完整表达式的组成部分。这与此处的分析不同,该分析认为它们各自都是完整表达式。

C++20的变化

指定初始化提案:P0329包含以下补充,似乎使得这个定义良好:

向11.6.1 [dcl.init.aggr]添加新的段落:

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

我们可以看到这在最新的标准草案中有所反映。


http://eel.is/c++draft/dcl.init.aggr#6呢?它非常明确地说允许这样做。 - Rakete1111
@Rakete1111,我记得这是最近添加的,等我有时间再查一下会更新的。 - Shafik Yaghmour
这些话的意思实际上是完全一样的,只是把“full-expression”换成了“_initializer-clause_”。无论如何,它们都会涵盖这种情况,或者都不会,但是不管怎样,P0507对排序没有任何影响。 P0329在段落之后解释了为什么要这样做(覆盖未显式初始化的元素,但这里并非如此)。 - T.C.
@T.C. P0329 的更改说明了“元素的初始化”,然后又说“与聚合体的给定元素相关的值计算和副作用”这与 [dcl.init.list]p4 不同,后者涉及到“初始化器子句”及其相关影响。 - Shafik Yaghmour
这不是重点。在 P0507 之前,它们可以说是完整表达式,“与完整表达式相关的每个值计算和副作用都在下一个要评估的完整表达式相关的每个值计算和副作用之前排序。” 在 P0507 之后,它们不再是完整表达式,但仍然“与给定 initializer-clause 相关的每个值计算和副作用都在逗号分隔的 initializer-list 列表中跟随它的任何 initializer-clause 相关的每个值计算和副作用之前排序。” - T.C.
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接