问题描述
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)
带有四个谓词 a,b,c,d
扩展为
( ( a(x) && b(x) ) && c(x) ) && d(x)
导致此评估顺序:a b c d
ps(x) && ...
扩展为
a(x) && ( b(x) && ( c(x) && d(x) ) )
导致相同的求值顺序:a b c d
这不会改变关于短路的任何事情;一旦为假,评估就停止。
,从 Pack && ...
的含义开始。
Cppreference 有一个非常易读的描述。
Pack && ...
变成 Pack1 && (Pack2 && (Pack3 && Pack4)))
... && Pack
变成 (((Pack1 && Pack2) && Pack3) && Pack4
在评估 &&
时,我们在分析树的顶部从左到右评估。
对于 Pack&&...
情况,此顶级 &&
是 Pack1
,然后是 and 运算符,然后是 (Pack2 && (Pack3 && Pack4))
。 &&
首先评估左侧,如果为 false 则停止。
对于 ...&&Pack
情况,顶级 &&
位于右侧。它的左手是(((Pack1 && Pack2) && Pack3)
,右手是Pack4
。
但是为了确定左手是否正确,我们继续应用该规则。我们最终评估的第一个术语是... Pack1
。如果它是假的,我们就不用去评估其余的了。
虽然树的形状不同,但它并不像人们想象的那么重要。
+
/ \
A +
/ \
B C
和
+
/ \
+ C
/ \
A B
在进行顺序遍历时以相同的顺序访问节点,左/右折叠只是切换生成这两个表达式树中的哪一个。
在某些情况下,左/右折叠很重要,但对 &&
求值的事物的 bool
不是其中之一。