25

假设我想引用initializer_list我已经定义的成员。我可以做吗?

此代码在 Visual Studio 和gcc中编译并给出预期的:“13 55” ,我只想知道它是合法的:

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

1 回答 1

20

因此,我们在这里拥有的是8.5.1C++ 标准草案部分中涵盖的聚合初始化,它说:

聚合是一个数组或一个类 [...]

和:

当聚合由初始化列表初始化时,如 8.5.4 中所指定,初始化列表的元素被视为聚合成员的初始化,按递增的下标或成员顺序。每个成员都从相应的初始化子句复制初始化 [...]

尽管初始化聚合的每个成员的副作用应该在下一个之前排序似乎是合理的,因为初始化列表中的每个元素都是一个完整的表达式。该标准实际上并不能保证这一点,我们可以从缺陷报告 1343中看到这一点,该报告说:

当前的措辞并不表示非类对象的初始化是一个完整的表达式,但大概应该这样做。

并注意:

聚合初始化也可能涉及多个完整表达式,因此上面对“非类对象的初始化”的限制是不正确的。

我们可以从相关的标准讨论主题中看到Richard Smith 说:

[intro.execution]p10:“完整表达式是不是另一个表达式的子表达式的表达式。[...]如果定义语言构造以产生函数的隐式调用,则使用语言构造被认为是本定义中的一种表达方式。”

由于花括号初始化列表不是表达式,并且在这种情况下它不会导致函数调用,因此 5 和 si 是单独的完整表达式。然后:

[intro.execution]p14:“与完整表达式关联的每个值计算和副作用都在与要评估的下一个完整表达式关联的每个值计算和副作用之前排序。”

所以唯一的问题是,初始化 si 的副作用是否与完整表达式“5”的评估“相关”?我认为唯一合理的假设是:如果 5 正在初始化类类型的成员,则构造函数调用显然是 [intro.execution] p10 中定义的完整表达式的一部分,因此很自然地假设标量类型也是如此。

但是,我认为该标准实际上并没有在任何地方明确说明这一点。

所以这目前没有被标准指定并且不能依赖,尽管如果实现没有按照您期望的方式对待它,我会感到惊讶。

对于像这样的简单情况,与此类似的东西似乎是更好的选择:

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

C++17 的变化

提案 P0507R0:核心问题 1343:非类初始化的排序澄清了这里提出的完整表达式点,但没有回答初始化的副作用是否包含在完整表达式的评估中的问题。所以它没有改变这是未指定的。

此问题的相关更改在[intro.execution]中:

组成表达式定义如下:

(9.1) — 一个表达式的组成表达式就是那个表达式。

(9.2) — 括号初始化列表或(可能带括号的)表达式列表的组成表达式是相应列表的元素的组成表达式。

(9.3) —形式为 = initializer-clause 的大括号或等式初始化器的组成表达式是初始化器子句的组成表达式。 [ 例子:

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

用于初始化 b 的初始化器的组成表达式是 5 和 1+1 。—结束示例]

[intro.execution]p12

一个完整的表达式是

(12.1) — 未计算的操作数(第 8 条),

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

(12.3) —一个 init-declarator (Clause 11) 或一个 mem-initializer (15.6.2),包括初始化器的组成表达式,

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

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

所以在这种情况下,13foo[0] + 42都是组成表达式,它们是完整表达式的一部分。这与此处的分析有所不同,该分析假定它们每个都是自己的完整表达式。

C++20 的变化

指定初始化提案:P0329包含以下添加内容,似乎对此进行了很好的定义:

在 11.6.1 [dcl.init.aggr] 中添加一个新段落:

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

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

于 2015-11-20T14:46:27.303 回答