有没有一种很好的方法来使用默认失败情况实现条件类型?

Sta*_*nny 14 c++ templates template-meta-programming c++11 conditional-types

对于实现条件类型,我非常喜欢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>>>>;
Run Code Online (Sandbox Code Playgroud)

使用它非常直观:

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
Run Code Online (Sandbox Code Playgroud)

但是,由于这是纯条件类型void,因此在这种情况下必须有默认类型- 。因此,如果N还有其他值,则该类型将产生:

bit_type<500u> f; // == void
Run Code Online (Sandbox Code Playgroud)

现在,它不会编译,但是yielding类型仍然有效。

意味着您可以说bit_type<500u>* f;并且将拥有一个有效的程序!

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


一个想法立即将取代过去std::conditional_tstd::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>>>>;
Run Code Online (Sandbox Code Playgroud)

这样做的问题是,模板总是被完全评估,这意味着模板总是std::enable_if_t被完全评估-如果失败N != std::size_t{ 64 }。嗯


我目前对此的解决方法相当笨拙,引入了一个struct和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>, "");
Run Code Online (Sandbox Code Playgroud)

通常可以使用,但是我不喜欢它,因为它添加了很多东西,我不妨只使用模板专门化。它也保留void为特殊类型-因此,它void实际上在分支机构产生的收益中不起作用。有没有可读的简短解决方案?

Jon*_*ely 18

您可以通过添加一个间接级别来解决此问题,以便最外面的结果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;
Run Code Online (Sandbox Code Playgroud)

在此版本中,final分支中的类型enable_if<condition, uint64_t>始终是有效类型,并且只有在该分支已被实际使用且enable_if<false, uint64_t>::type需要该分支时,您才会收到错误消息。当采用较早的分支之一时,最终会使用identity<uintNN_t>::type较小的整数类型之一,并且enable_if<false, uint64_t>没有嵌套类型也没关系(因为您不使用它)。

  • @MarekR不,那样,“条件”对(:不存在的):: type起作用。然后整个“身份”是多余的。 (4认同)
  • @MarekR,但我没有将`intN_t`和`enable_if`混合使用,而是将`identity &lt;intN_t&gt;`和`enable_if &lt;C,intN_t&gt;`混合使用。因为您需要添加一个间接级别。阅读问题。阅读答案。您的版本更简单**,但不起作用:https://wandbox.org/permlink/0LKosKXj50Y9hX1j** (3认同)
  • @MarekR`conditional_t`获取要应用`:: type`的类型,因此您将以其他方式编写`typename conditional &lt;...&gt; :: type :: type`。如果听起来令人困惑,那就是! (2认同)
  • @MarekR而不是尝试建议修复程序,请编译我的代码,然后尝试编译“固定”版本。您会发现哪些符合OP的要求,哪些不符合OP的要求。我的回答确实说明了为什么这样做,以及为什么有效。 (2认同)

max*_*x66 6

只是为了好玩...使用std::tuplestd::tuple_element避免使用std::conditional呢?

如果可以使用C ++ 14(因此可以使用模板变量和模板变量的特殊化功能),则可以编写用于转换大小/元组索引的模板变量

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; 
Run Code Online (Sandbox Code Playgroud)

所以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>>;
Run Code Online (Sandbox Code Playgroud)

如果只能使用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>, "!" );
Run Code Online (Sandbox Code Playgroud)

并且使用bit_type不受支持的尺寸

bit_type<42u> * pbt42;
Run Code Online (Sandbox Code Playgroud)

导致编译错误。

-编辑-正如乔纳森·韦克利(Jonathan Wakely)所建议的那样,如果您可以使用C ++ 20,那么std::ispow2()and std::log2p1()可以简化很多:您可以完全避免bt_index,只需编写

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>>;
Run Code Online (Sandbox Code Playgroud)

  • @JonathanWakely-正是我想要的;谢谢!给定`std :: ispow2()`和`std :: log2p1()`,您完全可以避免使用bt_index并将表达式直接用作std :: tuple_element的第一个参数。 (2认同)