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
?
source to share
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.
source to share