Is there a nice way to implement a conditional type with default fail case?
You can solve this by adding a level of indirection, so that the result of the outermost conditional_t
is not a type but a metafunction that needs ::type
to be applied to it. Then use enable_if
instead of enable_if_t
so you don't access the ::type
unless it's actually needed:
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;
In this version the type in the final branch is enable_if<
condition
, uint64_t>
which is always a valid type, and you only get an error if that branch is actually taken and enable_if<false, uint64_t>::type
is needed. When one of the earlier branches is taken you end up using identity<uintNN_t>::type
for one of the smaller integer types, and it doesn't matter that enable_if<false, uint64_t>
has no nested type (because you don't use it).
Just for fun... what about using std::tuple
and std::tuple_element
avoiding at all std::conditional
?
If you can use C++14 (so template variables and specialization of template variables) you can write a template variable for conversion 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;
so bit_type
become
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>>;
If you can use only C++11, you can develop a bt_index()
constexpr
function that return the correct (or incorrect) value.
You can verify that are satisfied
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>, "!" );
and that using bit_type
with an unsupported dimension
bit_type<42u> * pbt42;
cause a compilation error.
-- EDIT -- As suggested by Jonathan Wakely, if you can use C++20, so std::ispow2()
and std::log2p1()
, you can simplify a lot: you can avoid bt_index
at all and simply write
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>>;