首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >评价顺序v != std::exchange(v,前身(V))

评价顺序v != std::exchange(v,前身(V))
EN

Stack Overflow用户
提问于 2022-11-28 13:52:06
回答 4查看 1.9K关注 0票数 30

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

今天,我发现我自己写这个在一个答案:

代码语言:javascript
复制
do {
    path.push_front(v);
} while (v != std::exchange(v, pmap[v]));

我更喜欢它,比如说

代码语言:javascript
复制
do {
    path.push_front(v);
    if (v == pmap[v])
        break;
    v= pmap[v];
} while (true);

希望是出于明显的原因。

但是,我对标准不太了解,我不得不担心lhs != rhs不能保证在左侧之前对右侧表达式进行完全的评估。这将使它成为一个同义的比较-根据定义,这将返回true

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

有人知道吗

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

PS。我意识到这是f(a,b)的一个特例,其中foperator!=。我试图使用这里找到的信息回答我自己的查询,但至今未能得出结论:

EN

回答 4

Stack Overflow用户

发布于 2022-11-28 14:10:42

C++17在序列上引入了规则。以前的UB现在已经被很好的定义了。这适用于函数调用的参数以及所选的各种操作符:

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

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

然而,内置的!=而不是排序(参见上面的链接)。将对函数调用进行排序,但不能保证评估的顺序:

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

(强调后加)

据我所知,即使您编写了一个包装器函数,您的编译器也不需要首先计算v,然后是std::exchange(v, pmap[v]),最后是equal(..)。我相信,倒转评估顺序会改变你的例子中的语义。

因此,可悲的是,尽管std::exchange很好,但在这种情况下,它不能保证做您需要它做的事情。

票数 19
EN

Stack Overflow用户

发布于 2022-11-28 16:35:18

对于内置的!=运算符,或至少以值表示第一个参数的重载(即operator !=(T, T)):

这是每个[intro.execution]/10的UB

除注意到的情况外,对单个运算符的操作数和单个表达式的子表达式的评估是不按顺序进行的。..。如果对存储器位置的副作用相对于在同一存储器位置上的另一个副作用或使用同一存储器位置中任何对象的值计算的另一个副作用没有排序,并且它们可能不是并发的,则行为是未定义的。

( !=运算符不会具有任何特殊的排序属性。)

对于!=类型的v是否重载不影响排序规则(因为您没有使用函数调用符号来调用它) ([over.match.oper]/2):

..。操作符表示法首先被转换成等价的函数调用表示法.然而,操作数是按照为内置操作符规定的顺序排列的.

(即使使用了函数调用表示法,操作数仍然是不确定顺序,这意味着没有UB,但也不能保证结果一致。)

在重载的情况下,通过引用获取操作数(例如operator !=(const T&, const T&)T::operator !=(const T&) const):

这种行为是明确的.

绑定引用(直接)不访问对象(如示例中的v ),也不对其调用成员函数,因此两个操作数之间没有冲突。在初始化函数参数([intro.execution]/11)之后,对函数体中发生的访问(实际比较)进行排序:

当调用函数.时,与任何参数表达式相关联的每个值计算和副作用.在执行被调用函数正文中的每个表达式或语句之前对其进行排序。

这也意味着比较总是在两个操作数的副作用之后进行。在您的示例中,这意味着您将始终将v的交换后值与exchange (v的上一次)返回的值进行比较。

在C++17之前,上述情况也是正确的,尽管原因略有不同。

在C++14中,与C++17 ([expr]/2)相反:

重载操作符遵守条款expr中指定的语法规则,但要求.计算顺序被函数调用规则所取代。

...but函数调用中的参数本身是未排序的,而不是不确定顺序的([expr.call]/8):

对后缀表达式和参数的计算都是不按顺序排列的。

(这些引文来自非规范性注释,但它们很好地说明了这一点。)

这意味着效果仍然是一样的:操作数是未排序的,UB在评估v时访问它的值,否则定义得很好。唯一的区别是,在第一种情况下显式使用函数调用语法并不会阻止UB。

票数 14
EN

Stack Overflow用户

发布于 2022-11-28 23:49:25

不管这是否有效,它几乎是完全不可读的代码--记住,我们编写的代码主要是为人类而不是编译器编写的!

一般来说,使用有副作用的条件往往很难读懂,因为我们的大脑没有能力同时做两件事:了解条件是对还是错,并跟踪变化。如果你想让别人容易地理解正在发生的事情,一次只做一件事。在您的示例中,您正在向其添加第三个维度:当某事发生变化时;使用条件(其中有一个函数调用可以更改某些内容,并将更改的结果与其他内容进行比较)是不可能读懂的:-)

如果你绝对必须使用这个成语,那么把这三个部分分开:

代码语言:javascript
复制
do {
    path.push_front(v);
} while ([&v,&pmap]() mutable
         {
           auto old_v = v;  
           v = pmap[v];
           return v != old_v;
         } ());

当然,这段代码是等价的,但我认为它要容易得多。尽管我要声明,如果不使用std::exchange() :-),要阅读替代代码仍然要困难。)

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/74601619

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档