v != std::exchange(v, previous(v)) 中的求值顺序

seh*_*ehe 37 c++ sequence-points language-lawyer

我一直在寻找更多适合的习语std::exchange

今天我发现自己在一个答案中写下了这个:

do {
    path.push_front(v);
} while (v != std::exchange(v, pmap[v]));
Run Code Online (Sandbox Code Playgroud)

我比说更喜欢它

do {
    path.push_front(v);
    if (v == pmap[v])
        break;
    v= pmap[v];
} while (true);
Run Code Online (Sandbox Code Playgroud)

希望有明显的原因。

然而,我对标准语言并不热衷,我不禁担心这并lhs != rhs不能保证右侧表达式在左侧表达式之前没有被完全求值。这将使其成为同义反复的比较 - 根据定义将返回true.

然而,代码确实运行正确,显然lhs首先进行评估。

有人知道吗

  • 标准是否保证该评估顺序
  • 如果最近的标准发生了变化,哪个标准版本首先指定了它?

附言。f(a,b)我意识到这是where fis的一个特例operator!=。我尝试使用此处找到的信息回答我自己的查询,但迄今为止未能得出结论:

bit*_*ask 20

C++17 引入了序列规则。以前的 UB 现在已经有了明确的定义。这适用于函数调用的参数以及精选的运算符:

之前排序是同一线程内的评估之间的不对称、传递、成对关系。

  • 如果 A 排序在 B 之前(或者,同等地,B 排序在 A 之后),则 A 的评估将在 B 的评估开始之前完成。

!=然而,内置函数没有排序(参见上面的链接)。函数调用将按顺序进行,但不保证计算的顺序:

  1. 在函数调用中,每个参数初始化的值计算和副作用相对于任何其他参数的值计算和副作用是不确定地排序的。

(强调已添加)

根据我的阅读,即使您编写了一个包装函数,您的编译器也不需要v先评估,然后std::exchange(v, pmap[v])评估equal(..)。我相信,颠倒求值顺序会改变示例中的语义。

遗憾的是,std::exchange在这种情况下,尽管很好,但不能保证它能满足您的需要。

  • @bitmask 不同的调用约定对评估顺序有不同的偏好。历史上最流行的一种是基于堆栈的 cdecl,它更喜欢从右到左。 (3认同)

duc*_*uck 16

如果!=调用v通过引用获取左参数 () 的重载比较运算符,则此处的求值顺序并不重要:(直接)绑定引用不会访问初始化程序表示的对象。实际的比较(在运算符函数的主体中)发生在两个操作数都被求值之后([intro.execution]/11):

调用函数 [...] 时,与任何参数表达式 [...] 关联的每个值计算和副作用都会在执行被调用函数体中的每个表达式或语句之前进行排序。

换句话说,保证比较的行为符合您的预期。


如果评估左侧涉及读取 的值v(如内置!=运算符的情况),或者按值获取它的重载(或需要转换以绑定引用),则此方法不成立。

在这种情况下,两个操作数未排序就变得很重要([intro.execution]/10):

除非另有说明,否则各个运算符的操作数和各个表达式的子表达式的求值都是无序的。

(该!=运算符没有任何特殊的排序属性。)

这里有 UB 的潜力:

如果内存位置上的副作用相对于同一内存位置上的另一个副作用或使用同一内存位置中任何对象的值的值计算而言是无序的,并且它们不是潜在并发的,则行为是未定义的。

然而,[intro.execution]/11适用:

对于每个函数调用或等待表达式F的求值,每个不在F内发生但在同一线程上求值并作为同一信号处理程序(如果有)的一部分的求值要么在F内发生的所有求值之前排序或在F内发生的所有评估之后排序。

通过脚注总结:

换句话说,函数执行不会相互交错。

v这意味着左侧的读取和右侧的修改(发生在 内std::exchange)不会冲突,并且实际上是不确定的排序:没有 UB,但也不能保证结果一致。


C++17 对求值顺序的规则进行了一些更改,但与此处无关:此答案也适用于 C++17 之前的版本。