The problem here is just a misconception of what's actually happening.
How to make short-circuit evaluation also available in fold expressions?
It is available in fold expressions. (args && ... )
follows the exactly the same rules as (a && b && c && d)
. That is, d
will only be evaluated if a
, b
, and c
all evaluate to truthy.
That's not the actual difference between your two cases.
false && (*pb = true); // ok at runtime.
AndL(false, (*pb = true)); // error at runtime!
While fold expressions do exactly the same thing as their non-fold counterparts, there's one important difference between these two statements. The first is just a statement-expression, the second is a function call. And all function arguments must be evaluated before the start of the body begins.
So the second is equivalent to:
auto&& a = false;
auto&& b = (*pb = true);
(FORWARD(a) && FORWARD(b));
It's that ordering that is causing the problem, not the fold expression (note: b
could be evaluated before a
).
In order to make this transparent, what you really need are lazy arguments. This is a feature in several languages (e.g. Scala), but not in C++. If you need 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 then make this arbitrarily complex - maybe only "unwrap" those types that are callable, otherwise assume that they're 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 really, the main point is that function argument evaluation precedes the function body, and the issue is not the fold expression itself.
constexpr bool AND(bool a, bool b) { return a && b; }
. The issue is that all arguments must be evaluated before calling the function, and it's their result that is passed in. – Hyetographpb
, all the*pb = true
is undefined behavior. – Hierogram*pb = true
is itself not aconstexpr
. – Crowbarnullptr
before dereferencing. – Hyetograph