折叠表达式中的短路

Posted

技术标签:

【中文标题】折叠表达式中的短路【英文标题】:Short circuiting in fold expressions 【发布时间】:2021-05-13 13:29:42 【问题描述】:

这是一个基于我给 here 的自我回答的自触发问题。

This 似乎非常令人信服地解释了为什么在 fold expressions 中可以使用逻辑运算符的短路,以及使用可变参数将折叠表达式包装在函数中这一事实似乎是非短路的(事实上,答案解释说,在短路发生在函数体内之前,是函数调用触发了所有参数的评估)。

但是,在我看来,以下代码证明(至少当折叠表达式中的参数为 2 时)不会发生短路:

#include <assert.h>
#include <optional>

constexpr auto all_r = [](auto const& ... ps)
    return [&ps...](auto const& x)
        return (ps(x) && ...);
    ;
;

constexpr auto all_l = [](auto const& ... ps)
    return [&ps...](auto const& x)
        return (... && ps(x));
    ;
;

constexpr auto has_value = [](std::optional<int> o)
    return o.has_value();
;
constexpr auto has_positive = [](std::optional<int> o)
    assert(o.has_value());
    return o.value() > 0;
;

int main() 
    assert(!(has_value(std::optional<int>) && has_positive(std::optional<int>)));
    //assert(!(has_positive(std::optional<int>) && has_value(std::optional<int>))); // expectedly fails at run-time


    assert(!all_r(has_value, has_positive)(std::optional<int>));
    assert(!all_l(has_value, has_positive)(std::optional<int>)); // I expected this to fail at run-time
    //assert(!all_r(has_positive, has_value)(std::optional<int>));
    //assert(!all_l(has_positive, has_value)(std::optional<int>)); // I expected this to succeed at run-time

【问题讨论】:

我不认为ps(x) &amp;&amp; ...... &amp;&amp; ps(x)&amp;&amp; 操作的关联性有任何改变。那么为什么要期待不同的行为呢? @prog-fh 这两种形式正是为了在一个方向或另一个方向上强加关联性。 但是只有两个参数会改变什么? (从三个开始,我看到了区别)我确实使用了不好的术语:不是关联性,而是评估顺序。 @prog-fh 我想我已经理解我的错误了。我错误地期望评估顺序在两种形式中被镜像,就像关联性被镜像一样。 【参考方案1】:

... &amp;&amp; ps(x) 与四个谓词 a, b, c, d 扩展为

( ( a(x) && b(x) ) && c(x) ) && d(x)

导致这种评估顺序:a b c d

ps(x) &amp;&amp; ... 扩展为

a(x) && ( b(x) && ( c(x) && d(x) ) )

这导致相同的评估顺序:a b c d

这不会改变任何关于短路的事情;一旦有一个为假,评估就会停止。

【讨论】:

【参考方案2】:

Pack &amp;&amp; ... 的含义开始。

Cppreference 的描述非常易读。

Pack &amp;&amp; ... 变为 Pack1 &amp;&amp; (Pack2 &amp;&amp; (Pack3 &amp;&amp; Pack4))) ... &amp;&amp; Pack 变为 (((Pack1 &amp;&amp; Pack2) &amp;&amp; Pack3) &amp;&amp; Pack4

在评估 &amp;&amp; 时,我们从左到右评估解析树的顶部。

对于Pack&amp;&amp;... 情况,此***&amp;&amp;Pack1,然后是and 运算符,然后是(Pack2 &amp;&amp; (Pack3 &amp;&amp; Pack4))&amp;&amp; 首先计算左侧,如果为假则停止。

对于...&amp;&amp;Pack 案例,顶层&amp;&amp; 位于右侧。它的左手是(((Pack1 &amp;&amp; Pack2) &amp;&amp; Pack3),右手是Pack4

但要确定左手是否为真,我们继续应用该规则。我们最终评估的第一个术语是...Pack1。如果它是假的,我们不会费心评估其余的。

虽然树的形状不同,但并不像人们想象的那么重要。

  +
 / \
A   +
   / \
  B   C

      +
     / \
    +   C
   / \
  A   B

在进行顺序遍历时,以相同的顺序访问节点,左/右折叠只是切换生成这两个表达式树中的哪一个。

在某些情况下左右折叠很重要,但 &amp;&amp; 评估为 bool 的东西不是其中之一。

【讨论】:

以上是关于折叠表达式中的短路的主要内容,如果未能解决你的问题,请参考以下文章

Java中的逻辑运算符短路效应

JOIN 子句中的 MySQL 逻辑评估是不是延迟/短路?

JS中的短路运算[转]

XPath 是不是对逻辑表达式进行短路评估?

如何在短路表达式中检索命令的返回码

C语言中的短路现象