对于实现条件类型,我非常喜欢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<500u>* f;
,并且会有一个有效的程序!
那么当达到条件类型的失败情况时,有没有一种好的方法让编译失败?
立即想到的一个方法是用std::enable_if_t
替换最后一个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::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
实际上是分支的收益的情况下,它将不起作用。有一个可读的,简短的解决方案吗?
答案 0 :(得分: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;
在此版本中,最终分支中的类型为enable_if<
condition
, uint64_t>
,它始终是有效类型,并且只有在该分支实际被采用并且{{ 1}}是必需的。当采用较早的分支之一时,您最终将enable_if<false, uint64_t>::type
用于较小的整数类型之一,并且identity<uintNN_t>::type
没有嵌套类型也没关系(因为您不使用它)
答案 1 :(得分:6)
只是为了好玩...如何使用std::tuple
和std::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;
所以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()
可以简化很多:可以完全避免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>>;