如果我写f(x)->g(args, ...)
,我可以在args, ...
求值之前依赖f(x)
之后的序列点吗?我可以看到两个参数:
- §1.9.17"当调用函数时(无论函数是否内联),在函数体中执行任何表达式或语句之前,在所有函数参数(如果有的话)的求值之后存在一个序列点。在复制返回值之后,在执行函数外的任何表达式之前,也有一个序列点。" 另一方面,对象指针隐式地是一个隐藏的参数
this
,就好像我写了g(f(x), args, ...)
,这表明它就像一个参数,因此未指定。->
运算符不是一个普通的二进制运算符,因为显然g(...)
不能像我写f(x) + g(...)
那样在f(x)
之前求值。我很惊讶我找不到关于它的具体说明。
答案取决于您正在使用(或您的编译器正在使用)的c++标准的版本。
c++ 2003 5.2.2 p8说:
参数的求值顺序未指定。参数表达式求值的所有副作用在进入函数之前生效。后缀表达式和实参表达式列表的求值顺序未指定。
这意味着在f(x)
和args
求值之间不存在序列点。
在c++ 2011中,序列点的整个概念已被替换(参见N1944),并且该措辞现在只是一个注释:
[注意:后缀表达式和实参表达式的求值都是相对于彼此的非排序的。参数表达式求值的所有副作用在进入函数之前被排序(见1.9)。- 结束说明]
和1.9 p15说
调用函数时(无论函数是否内联),与任何实参表达式或指定被调用函数的后缀表达式相关的每个值计算和副作用,都在执行被调用函数体中的每个表达式或语句之前进行排序。[注意:与不同参数表达式相关的值计算和副作用是不排序的。- 结束说明]
这表示表达式f(x)
和表达式args
在g
主体的所有内容之前排序,但它们相对于彼此是未排序的,这与c++ 03规则相同,但措辞不同。
c++ 14有与c++ 11相同的规则,但是在下面的注释中指出,这些规则在c++ 17中发生了变化。
c++ 2017 8.2.2p5说:
后缀表达式排在表达式列表中的每个表达式和任何默认实参之前。参数的初始化,包括每个相关的值计算和副作用,相对于任何其他参数的初始化顺序是不确定的。
这意味着在您的示例中按顺序执行以下步骤:
-
f
被评估。 对 -
x
求值,并初始化f
的参数。 - 函数调用
f(x)
被求值。 -
f(x)->g
被评估。 -
args
和g
的其他参数被求值,g
的参数被初始化(顺序未指定)。 - 最后,对函数调用
f(x)->g(args, ...)
求值。
注意,我认为你在标题中问了一个问题,而在问题正文中问了另一个问题。
嗯,这并不矛盾。要对函数求值,必须执行以下操作(不一定按照这个顺序)。
- x被评估(A)
- 参数被评估(B)
- …(C)
- f(x)称为(D)
- f(x)的返回值被复制(E)
- return->g(args,…)被称为(F)
现在,你引用的规则表明
- (A)必须发生在(D)之前,因为在求值之前有一个对函数的参数求值的序列点。
- (D)发生在(E)之前,因为直到函数运行才能进行复制。
- (F)发生在(E)之后,因为需要隐式指针来调用g(args) *
- (B)和(C)发生在(F)之前,因为它们是参数。
然而,未排序的是(A), (B)和(C)之间的关系,或者在你的问题中(B)和(C)和(D)之间的关系,因为它们不是(F)的参数,它们可以在之后求值。或者,它们可以事先评估。
*有趣的问题。如果g(args,…)是静态成员函数会发生什么?在这种情况下,由于从f(x)返回的指针实际上没有传入,它可以更早排序吗?但这是另一个问题