How do I make "short-circuit evaluation" also available in `fold expression`?
#include <type_traits>
#define FORWARD(arg)\
std::forward<decltype(arg)>(arg)
template<typename... Args>
constexpr bool AndL(Args&&... args)
{
return (... && FORWARD(args));
}
template<typename... Args>
constexpr bool AndR(Args&&... args)
{
return (FORWARD(args) && ...);
}
int main()
{
bool* pb = nullptr;
false && (*pb = true); // ok at runtime.
AndL(false, (*pb = true)); // error at runtime!
AndR(false, (*pb = true)); // error at runtime!
}
The legacy operator &&
supports short circuit evaluation , so it false && (*pb = true)
will be fine at runtime, but not the following two cases.
How to make short circuit assessment also available in fold expressions
?
The problem here is simply a misconception of what is really going on.
How to make the short circuit assessment also available in folding terms?
It is available in fold expressions. (args && ... )
follows exactly the same rules as (a && b && c && d)
. That is, it d
will only be judged if a
, b
and c
are judged to be true.
This is not a real difference between your two cases.
false && (*pb = true); // ok at runtime.
AndL(false, (*pb = true)); // error at runtime!
While flexing expressions do the same thing as non-multiple copies of them, there is one important difference between the two. The first is just an expression expression, the second is a function call. And all the function arguments must be evaluated before the start of the body.
So the second is equivalent to:
auto&& a = false;
auto&& b = (*pb = true);
(FORWARD(a) && FORWARD(b));
This is what the call is causing, not the fold expression (note: b
can be evaluated before a
).
To make this transparent, you really need lazy arguments. This is a feature in multiple languages (like Scala ) but not C ++. If you want laziness, the best you could do is wrap everything in a lambda:
template<typename... Args>
constexpr bool AndL(Args&&... args)
{
return (... && FORWARD(args)());
}
AndL([]{ return false; }, [&]{ return *pb = true; });
You could make this arbitrarily complex - perhaps just "expand" the types you want to call, otherwise let's assume they are bool:
template <class T, std::enable_if_t<std::is_invocable<T>::value, int> = 0>
bool unwrap(T&& val) { return std::forward<T>(val)(); }
template <class T, std::enable_if_t<std::is_convertible<T, bool>::value, int> = 0>
bool unwrap(T&& val) { return std::forward<T>(val); }
template<typename... Args>
constexpr bool AndL(Args&&... args)
{
return (... && unwrap(FORWARD(args)));
}
AndL(false, [&]{ return *pb = true; });
But in fact, the main thing is that the evaluation of the function argument precedes the body of the function, and the problem is not the expression itself.