是的,这在标准中有所涵盖,但它是未指定的行为。最近的C++标准提案N4228: Refining Expression Evaluation Order for Idiomatic C++涵盖了这种情况,旨在完善评估顺序规则,使其对某些情况进行明确定义。
它描述了这个问题如下:
Expression evaluation order is a recurring discussion topic in the C++
community. In a nutshell, given an expression such as f(a, b,
c), the order in which the sub-expressions f, a, b, c are evaluated is left unspecified by the standard. If any two of these sub-expressions happen to modify the same object without intervening sequence points, the behavior of the program is undefined. For instance, the expression f(i++, i) where i is an
integer variable leads to undefined behavior , as does v[i]
= i++. Even when the behavior is not undefined, the result of evaluating an expression can still be anybody’s guess. Consider
the following program fragment:
#include <map>
int main() {
std::map<int, int> m;
m[0] = m.size();
}
What should the map object m look like after evaluation of the
statement marked #1? { {0, 0 } } or {{0, 1 } } ?
我们知道,如果没有指定子表达式的评估顺序,则其是无序的。这来自于
draft C++11 standard第
1.9
节
程序执行,其中写道:
除非另有说明,否则单个运算符的操作数和单个表达式的子表达式的求值是无序的。[...]
而所有的
5.17
节赋值和复合赋值运算符[expr.ass]所说的是:
[...]在所有情况下,赋值都在右侧和左侧操作数的值计算之后进行,并在赋值表达式的值计算之前进行。[...]
因此,这一节并没有确定评估顺序,但我们知道这不是未定义行为,因为
operator []
和
size()
都是函数调用,而第
1.9
节告诉我们(
我强调):
当调用函数(无论函数是否内联)时,与指定调用的后缀表达式或被调用函数相关的所有参数表达式的值计算和副作用都在调用函数主体中的每个表达式或语句执行之前排序。[注意:不同参数表达式相关的值计算和副作用是未排序的。——end note] 在调用函数的主体执行之前,与调用函数相关的每个表达式或语句中没有明确定序的评估与被调用函数的执行顺序不确定地排序。9 [...]
请注意,我在问题
“The C++ Programming Language”第4版第36.3.6节中的此代码是否具有定义良好的行为?中涵盖了提案N4228的第二个有趣的示例。
更新
我看起来像是 N4228
的修订版本在最近的WG21会议上被进化工作组接受, 但该论文(P0145R0)还没有公开。因此,这可能不再是C++17中未指定的内容。
更新2
P0145的第三版将其指定,并更新了 [expr.ass]p1:
赋值运算符(=)和复合赋值运算符都是从右到左进行分组的。
它们的左操作数必须是可修改的左值;它们的结果是引用左操作数的左值。
如果左操作数是位域,则在所有情况下结果都是位域。
在所有情况下,赋值在右操作数和左操作数的值计算之后进行序列化,并在赋值表达式的值计算之前进行序列化。
右操作数在左操作数之前进行序列化。 ...
map::insert
之前必须完成std::pair
的构造函数。 - Mats Petersson