【问题标题】:Is there a nice way to implement a conditional type with default fail case?有没有一种很好的方法来实现具有默认失败情况的条件类型?
【发布时间】:2019-11-17 19:02:12
【问题描述】:

对于实现条件类型,我非常喜欢std::conditional_t,因为它使代码简短且可读性强:

template<std::size_t N>
using bit_type =
    std::conditional_t<N == std::size_t{  8 }, std::uint8_t,
    std::conditional_t<N == std::size_t{ 16 }, std::uint16_t,
    std::conditional_t<N == std::size_t{ 32 }, std::uint32_t, 
    std::conditional_t<N == std::size_t{ 64 }, std::uint64_t, void>>>>;

使用它非常直观:

bit_type<8u> a;  // == std::uint8_t
bit_type<16u> b; // == std::uint16_t
bit_type<32u> c; // == std::uint32_t
bit_type<64u> d; // == std::uint64_t

但由于这是一个纯条件类型,因此必须有一个默认类型 - void,在这种情况下。因此,如果 N 是任何其他值,则表示类型会产生:

bit_type<500u> f; // == void

现在这不会编译,但产生的类型仍然有效。

意思是你可以说bit_type&lt;500u&gt;* f; 并且会有一个有效的程序!

那么有没有一种好方法可以在达到条件类型的失败情况时让编译失败?


一个想法是立即将最后一个std::conditional_t 替换为std::enable_if_t

template<std::size_t N>
using bit_type =
    std::conditional_t<N == std::size_t{  8 }, std::uint8_t,
    std::conditional_t<N == std::size_t{ 16 }, std::uint16_t,
    std::conditional_t<N == std::size_t{ 32 }, std::uint32_t, 
    std::enable_if_t<  N == std::size_t{ 64 }, std::uint64_t>>>>;

问题在于模板总是被完全评估,这意味着std::enable_if_t总是被完全评估——如果N != std::size_t{ 64 }会失败。呃。


我目前的解决方法是引入一个结构和 3 个using 声明相当笨拙:

template<std::size_t N>
struct bit_type {
private:
    using vtype =
        std::conditional_t<N == std::size_t{ 8 }, std::uint8_t,
        std::conditional_t<N == std::size_t{ 16 }, std::uint16_t,
        std::conditional_t<N == std::size_t{ 32 }, std::uint32_t,
        std::conditional_t<N == std::size_t{ 64 }, std::uint64_t, void>>>>;

public:
    using type = std::enable_if_t<!std::is_same_v<vtype, void>, vtype>;
};

template<std::size_t N>
using bit_type_t = bit_type<N>::type;

static_assert(std::is_same_v<bit_type_t<64u>, std::uint64_t>, "");

这通常有效,但我不喜欢它,因为它添加了很多东西,我还不如只使用模板专业化。它还将void 保留为特殊类型 - 因此它在void 实际上是分支的产量时不起作用。有没有可读的简短解决方案?

【问题讨论】:

  • 为什么是N == std::size_t{8} 而不是N == 8?是不是怕std::size_t 不能持有8
  • @L.F.可能他要防止警告比较具有不同符号的类型。
  • @MarekR 嗯...我在-Wall -Wextra -Werror -pedantic-errors 下没有收到N == 8 的警告。对于可能不会导致问题的文字,可能会自动抑制警告?
  • 我总是尽量保持较低的转换次数,我知道在恒定的编译时间上下文中没有必要 - 但这是我的习惯。

标签: c++ c++11 templates template-meta-programming conditional-types


【解决方案1】:

您可以通过添加一个间接级别来解决这个问题,这样最外面的conditional_t 的结果就不是一个类型,而是一个需要::type 应用到它的元函数。然后使用enable_if 而不是enable_if_t,这样您就不会访问::type,除非确实需要它:

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

template<std::size_t N>
using bit_type = typename
    std::conditional_t<N == std::size_t{  8 }, identity<std::uint8_t>,
    std::conditional_t<N == std::size_t{ 16 }, identity<std::uint16_t>,
    std::conditional_t<N == std::size_t{ 32 }, identity<std::uint32_t>, 
    std::enable_if<N == std::size_t{ 64 }, std::uint64_t>>>>::type;

在这个版本中,最终分支中的类型是enable_if&lt;<i>condition</i>, uint64_t&gt;,它始终是一个有效的类型,并且只有在实际采用该分支并且需要enable_if&lt;false, uint64_t&gt;::type 时才会出现错误。当采用较早的分支之一时,您最终将 identity&lt;uintNN_t&gt;::type 用于较小的整数类型之一,而 enable_if&lt;false, uint64_t&gt; 没有嵌套类型并不重要(因为您不使用它)。

【讨论】:

  • 所以_t 版本实际上有缺点的情况很少见。有趣,谢谢!
  • @MarekR 不,这样conditional 在(不存在的)::type 上运行。然后整个identity 是多余的。
  • @MarekR conditional_t 获取应用::type 的类型,因此您将编写typename conditional&lt;...&gt;::type::type。如果这听起来令人困惑,那就是!
  • @MarekR 不要尝试建议修复,请编译我的代码,并尝试编译您的“修复”版本。您会发现哪一个符合 OP 的要求,而哪一个不符合。我的回答确实解释了为什么这样做,以及为什么会起作用。
  • @MarekR 但我不混合使用intN_tenable_if,我混合使用identity&lt;intN_t&gt;enable_if&lt;C, intN_t&gt;。因为您需要添加一个间接级别。阅读问题。阅读答案。您的版本更简单但不起作用:wandbox.org/permlink/0LKosKXj50Y9hX1j
【解决方案2】:

只是为了好玩...使用std::tuplestd::tuple_element 来避免std::conditional 怎么样?

如果你可以使用 C++14(所以模板变量和模板变量的特化)你可以写一个模板变量来转换 size/index-in-the-tuple

template <std::size_t>
constexpr std::size_t  bt_index = 100u; // bad value

template <> constexpr std::size_t  bt_index<8u>  = 0u; 
template <> constexpr std::size_t  bt_index<16u> = 1u; 
template <> constexpr std::size_t  bt_index<32u> = 2u; 
template <> constexpr std::size_t  bt_index<64u> = 3u; 

所以bit_type变成了

template <std::size_t N>
using bit_type = std::tuple_element_t<bt_index<N>,
   std::tuple<std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t>>;

如果你只能使用 C++11,你可以开发一个 bt_index() constexpr 函数来返回正确(或不正确)的值。

您可以验证是否满意

static_assert( std::is_same_v<bit_type<8u>,  std::uint8_t>, "!" );
static_assert( std::is_same_v<bit_type<16u>, std::uint16_t>, "!" );
static_assert( std::is_same_v<bit_type<32u>, std::uint32_t>, "!" );
static_assert( std::is_same_v<bit_type<64u>, std::uint64_t>, "!" );

以及使用 bit_type 的尺寸不受支持

bit_type<42u> * pbt42;

导致编译错误。

-- 编辑-- 正如 Jonathan Wakely 所建议的,如果你可以使用 C++20,那么 std::ispow2()std::log2p1(),你可以简化很多:你可以避免 @987654334 @ 并且简单地写

template <std::size_t N>
using bit_type = std::tuple_element_t<std::ispow2(N) ? std::log2p1(N)-4u : -1,
   std::tuple<std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t>>;

【讨论】:

  • 不错的选择!我想由于std::tuple 的对齐性质,它可能会增加大型案例的编译时间,这里不需要。
  • 注:在 C++20 中,您应该能够做到 template &lt;std::size_t N&gt; constexpr std::size_t bt_index = std::ispow2(N) ? std::log2p1(N) - 4 : -1;(即无需专门化变量模板)。
  • @JonathanWakely - 正是我想要的;谢谢!给定std::ispow2()std::log2p1(),您可以完全避免bt_index,直接使用您的表达式作为std::tuple_element 的第一个参数。
  • 你确实可以:-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-11-15
  • 2012-04-17
  • 2021-09-21
  • 1970-01-01
  • 2019-10-20
相关资源
最近更新 更多