折叠表达式短路
Short circuiting in fold expressions
这是一个基于我给出的自我回答的自我触发问题 。
seems a pretty convincing explanation of why short-circuiting of logical operators is available in 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
}
从 Pack && ...
的含义开始。
Cppreference 有一个非常易读的描述。
Pack && ...
变为 Pack1 && (Pack2 && (Pack3 && Pack4)))
... && Pack
变为 (((Pack1 && Pack2) && Pack3) && Pack4
在计算 &&
时,我们在解析树的顶部从左到右计算。
对于 Pack&&...
的情况,这个顶级 &&
是 Pack1
然后是 and 运算符,然后是 (Pack2 && (Pack3 && Pack4))
。 &&
首先评估左侧,如果为假则停止。
对于 ...&&Pack
案例,顶层 &&
在右边。它的左手是(((Pack1 && Pack2) && Pack3)
,右手是Pack4
。
但是为了确定左手是否正确,我们继续应用该规则。我们最终评估的第一个术语是... Pack1
。如果它是假的,我们就不会费心评估其余部分。
虽然树的形状不同,但也没有想象中那么重要
+
/ \
A +
/ \
B C
和
+
/ \
+ C
/ \
A B
按顺序遍历时按相同顺序访问节点,左/右折叠只是切换生成这两个表达式树中的哪一个。
在某些情况下 left/right 折叠很重要,但 &&
评估为 bool
的事情不是其中之一。
... && 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
这不会改变短路的任何内容;一旦一个错误,评估就会停止。
这是一个基于我给出的自我回答的自我触发问题
但是,在我看来,以下代码证明(至少当折叠表达式中的参数为 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
}
从 Pack && ...
的含义开始。
Cppreference 有一个非常易读的描述。
Pack && ...
变为 Pack1 && (Pack2 && (Pack3 && Pack4)))
... && Pack
变为 (((Pack1 && Pack2) && Pack3) && Pack4
在计算 &&
时,我们在解析树的顶部从左到右计算。
对于 Pack&&...
的情况,这个顶级 &&
是 Pack1
然后是 and 运算符,然后是 (Pack2 && (Pack3 && Pack4))
。 &&
首先评估左侧,如果为假则停止。
对于 ...&&Pack
案例,顶层 &&
在右边。它的左手是(((Pack1 && Pack2) && Pack3)
,右手是Pack4
。
但是为了确定左手是否正确,我们继续应用该规则。我们最终评估的第一个术语是... Pack1
。如果它是假的,我们就不会费心评估其余部分。
虽然树的形状不同,但也没有想象中那么重要
+
/ \
A +
/ \
B C
和
+
/ \
+ C
/ \
A B
按顺序遍历时按相同顺序访问节点,左/右折叠只是切换生成这两个表达式树中的哪一个。
在某些情况下 left/right 折叠很重要,但 &&
评估为 bool
的事情不是其中之一。
... && 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
这不会改变短路的任何内容;一旦一个错误,评估就会停止。