C++ 元函数(例如 std::conditional)的评估策略(eager、lazy、...)是什么?

nod*_*kai 1 c++ metaprogramming operator-precedence template-meta-programming evaluation-strategy

C++14 草案 n4140 读取

T 应为枚举类型

template <class T> struct underlying_type

写作有多糟糕

std::conditional_t<std::is_enum<T>::value, std::underlying_type_t<T>, foo>
Run Code Online (Sandbox Code Playgroud)

什么时候T可以是任意类型?我会进入 UB 吗,编译器会删除我的 $HOME (因为语言律师说“在 UB 下任何事情都可能发生”)?

Bar*_*rry 5

我会踏上 UB [...]

从技术上讲,是的。但实际上,它不会为非枚举类型编译。当你写:

std::conditional_t<std::is_enum<T>::value, std::underlying_type_t<T>, foo>;    
                                           ^^^^^^^^^^^^^^^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)

在实例化模板之前,必须评估该模板参数conditional。这相当于在函数体开始之前必须调用所有函数参数。对于非枚举类型,underlying_type<T>是不完整的(确保在标准中将其指定为 undefined 但我们要合理),因此没有underlying_type_t. 所以实例化失败。

在这种情况下,您需要做的是延迟实例化:

template <class T> struct tag { using type = T; };

typename std::conditional_t<
    std::is_enum<T>::value,
    std::underlying_type<T>,
    tag<foo>>::type;
Run Code Online (Sandbox Code Playgroud)

现在,我们conditional不是选择类型而是选择元函数!underlying_type<T>::type只会被实例化为T一个枚举。我们还必须包装foo以将其转换为元函数。

这是一个常见的模式,在 Boost.MPL 中是一个特殊的东西,叫做eval_if,它看起来像:

template <bool B, class T, class F>
using eval_if_t = typename std::conditional_t<B, T, F>::type;
Run Code Online (Sandbox Code Playgroud)

请注意,我们都在使用conditional_t ::type