7
#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!
}

传统&&算子支持短路求值,所以false && (*pb = true)在运行时会ok,但下面两种情况不行。

如何使短路评估也可用fold expressions

4

1 回答 1

17

这里的问题只是对实际发生的事情的误解。

如何在折叠表达式中也可以使用短路评估?

它在折叠表达式中可用。(args && ... )遵循与 完全相同的规则(a && b && c && d)。也就是说,d仅当abc全部评估为真时才会评估。

这不是你的两个案例之间的实际区别。

false && (*pb = true);       // ok at runtime.
AndL(false, (*pb = true));   // error at runtime!

虽然折叠表达式与非折叠表达式的作用完全相同,但这两个语句之间存在一个重要区别。第一个只是一个语句表达式,第二个是函数调用。并且必须在主体开始之前评估所有函数参数。

所以第二个相当于:

auto&& a = false;
auto&& b = (*pb = true);
(FORWARD(a) && FORWARD(b));

这是导致问题的排序,而不是折叠表达式(注意:b可以在之前评估a)。

为了使这一点透明,您真正需要的是惰性参数。这是几种语言(例如Scala)的一个特性,但在 C++ 中没有。如果你需要懒惰,你能做的最好的就是把所有东西都包装在一个 lambda 中:

template<typename... Args>
constexpr bool AndL(Args&&... args)
{
    return (... && FORWARD(args)());
}

AndL([]{ return false; }, [&]{ return *pb = true; });

然后你可以使这个任意复杂 - 可能只“解包”那些可调用的类型,否则假设它们是 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; });

但实际上,重点是函数参数评估在函数体之前,问题不在于折叠表达式本身。

于 2017-03-27T14:05:31.937 回答