std::conditional 等 C++ 元函数的求值策略(eager,lazy,...)是什么?

What is the evaluation strategy (eager, lazy, ...) of C++ metafunctions such as std::conditional?

C++14 草案 n4140 读取

T shall be an enumeration type

对于 template <class T> struct underlying_type

写的有多烂

std::conditional_t<std::is_enum<T>::value, std::underlying_type_t<T>, foo>

什么时候T可以是任意类型?我会进入 UB 并且编译器会删除我的 $HOME(因为语言律师说 "anything can happen under an UB")吗?

Will I step onto an UB [...]

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

std::conditional_t<std::is_enum<T>::value, std::underlying_type_t<T>, foo>;    
                                           ^^^^^^^^^^^^^^^^^^^^^^^^^

conditional 模板可以实例化之前,必须评估该模板参数。这相当于必须在函数体开始之前调用所有函数参数。对于非枚举类型,underlying_type<T> 是不完整的(当然在标准中指定为未定义,但让我们讲道理),所以没有 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;

现在,我们的 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;

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