#define FORWARD(arg)\ std::forward

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

?

+3


source to share


1 answer


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.

+11


source







All Articles