std::disjunction 中的短路模板专业化

Short-circuiting template specialization in std::disjunction

一些背景:

我正在努力组装一个模板化的 class,作为模板专业化的一部分,它推导了一种类型以供其成员之一使用。这个数据成员需要支持通过网络流式传输,我正在努力使系统尽可能灵活和可扩展(目标是可以通过修改专业化的一些高级元素来创建该类型的新变体逻辑而不进入实现代码的内部)。一些现有的用法将此数据成员专门化为枚举,流式代码支持将此值来回转换为 32 位整数以通过线路传输。

因为枚举可以定义(隐式或显式)以由不同类型支持——在 64 位值的情况下最危险——我希望能够强制执行,如果已解析的类型是枚举,它的基础类型必须是 32 位整数(更一般地说,我只需要强制它是 32 位的 maximum,但我会担心一旦更简单的案例起作用,就会变得复杂。

我尝试的解决方案:

type_traits 提供的一些工具拼接在一起,我得出了以下结论:

#include <cstdint>
#include <type_traits>

using TestedType = /* Logic for deducing a type */;
constexpr bool nonEnumOrIntBacked =
    !std::is_enum_v<TestedType>
    || std::is_same_v<std::underlying_type_t<TestedType>, std::int32_t>;
static_assert(nonEnumOrIntBacked,
    "TestedType is an enum backed by something other than a 32-bit integer");

但是,当我尝试编译它时(在最新更新中使用 Visual Studio 2017),我遇到了错误文本 'TestedType': only enumeration type is allowed as an argument to compiler intrinsic type trait '__underlying_type'。看到这一点,我尝试了一种使用 std::disjunction 的替代公式,我认为如果较早的条件评估为真,它应该对模板进行短路评估(我省略了 std 限定符以使其更可读):

disjunction_v<
    negation<is_enum<TestedType>>,
    is_same<underlying_type_t<TestedType>, int32_t>
>;

我也尝试过将 underlying_type_t 的违规用法包装在一个 enable_if 中,以枚举类型为前提,但也没有成功。

我的问题:

一般的布尔运算符(尤其是 std::disjunction)不是对模板进行短路评估吗?在 the cppreference page for std::disjunction 上,它声明如下(强调我的):

Disjunction is short-circuiting: if there is a template type argument Bi with bool(Bi::value) != false, then instantiating disjunction::value does not require the instantiation of Bj::value for j > i

读到这里,我原以为 underlying_type_t<TestedType> 对于某些非枚举类型的格式错误的性质是无关紧要的,因为一旦上游的某些东西被评估为,就不需要考虑下游类型是的。

如果我对规范的阅读在这一点上不正确,是否有另一种方法可以在编译时完成此检查,或者我是否需要添加运行时检查来强制执行此操作?

模板参数 "evaluated" 就像常规参数一样急切。导致问题的部分是 underyling_type_t,但它在两个版本中都完整存在。您需要将该部分延迟到知道 TestedType 是枚举类型之后。这对于 constexpr if (live example):

来说相当简单
template<typename T>
constexpr bool nonEnumOrIntBackedImpl() {
    if constexpr (std::is_enum_v<T>) {
        return std::is_same_v<std::underlying_type_t<T>, std::int32_t>;
    } else {
        return false;
    }
}

template<typename T>
constexpr bool nonEnumOrIntBacked = nonEnumOrIntBackedImpl<T>();

在 C++17 之前,一种常见的方法是标签调度 (live example):

template<typename T>
constexpr bool nonEnumOrIntBackedImpl(std::true_type) {
    return std::is_same<std::underlying_type_t<T>, std::int32_t>{};
}

template<typename T>
constexpr bool nonEnumOrIntBackedImpl(std::false_type) {
    return false;
}

template<typename T>
constexpr bool nonEnumOrIntBacked = nonEnumOrIntBackedImpl<T>(std::is_enum<T>{});

为什么构造失败并给出了解决方案。但本着利用库的每一个特性的精神,这里是如何使用短路

template<typename T, typename I>
struct is_underlying
{
    static constexpr auto value =
        std::is_same_v<std::underlying_type_t<T>, I>;
};

using TestedType = int;
constexpr bool nonEnumOrIntBacked =
    std::disjunction_v<std::negation<std::is_enum<TestedType>>, 
        is_underlying<TestedType, std::int32_t>>;

模板需要格式正确,但 value

备选方案:

template <typename T> struct identity { using type = T; };

bool nonEnumOrIntBacked =
    std::is_same<
         std::conditional_t<
             std::is_enum_v<TestedType>,
             std::underlying_type<TestedType>,
             identity<void>>::type,
         int
    >::value
;

conditional<cont, type1, type2>::type::type :) 延迟评估。