Search code examples
c++c++14

How to use std::conditional such that all options have a check / no option is the default


std::conditional_t can be nested perpetually:

#include <cstdint>
#include <cstring>
#include <type_traits>

enum class data_types { single_bytes, four_byte_integrals, four_byte_floats };
    
template<data_types expected_type>
std::conditional_t<expected_type == data_types::single_bytes,        uint8_t,
std::conditional_t<expected_type == data_types::four_byte_integrals, int32_t,
                                                                     float>>
parse(const uint8_t * rawBytes)
{
    using return_t = std::conditional_t<expected_type == data_types::single_bytes,        uint8_t,
                     std::conditional_t<expected_type == data_types::four_byte_integrals, int32_t,
                                                                                          float>>;
    constexpr auto length = sizeof(return_t);
    return_t result;
    std::memcpy(&result, rawBytes, length);
    return result;
}

However, this leaves the last option as the default case, if all conditions fail. This may be undesirable.

std::enable_if exists to prevent template instantiation if a condition is not fulfilled:

#include <cstdint>
#include <cstring>
#include <type_traits>

enum class data_types { single_bytes, four_byte_integrals, four_byte_floats };

template<data_types expected_type>
std::enable_if_t<expected_type == data_types::four_byte_floats , float>
parse(const uint8_t * rawBytes)
{
    float result;
    constexpr auto length = sizeof(float);
    std::memcpy(&result, rawBytes, length);
    return result;
}

But nesting std::enable_if into std::conditional fails always, even if an earlier branch would suffice (std::conditional does not short-circuit):

#include <cstdint>
#include <cstring>
#include <type_traits>

enum class data_types { single_bytes, four_byte_integrals, four_byte_floats };
    
template<data_types expected_type>
std::conditional_t<expected_type == data_types::single_bytes,        uint8_t,
std::conditional_t<expected_type == data_types::four_byte_integrals, int32_t,
std::enable_if_t<expected_type == data_types::four_byte_floats,      float>>>
parse(const uint8_t * rawBytes)
{
    using return_t = std::conditional_t<expected_type == data_types::single_bytes,        uint8_t,
                     std::conditional_t<expected_type == data_types::four_byte_integrals, int32_t,
                     std::enable_if_t<expected_type == data_types::four_byte_floats,      float>>>;
    constexpr auto length = sizeof(return_t);
    return_t result;
    std::memcpy(&result, rawBytes, length);
    return result;
}

emits "error: no type named 'type' in 'struct std::enable_if<false, float>'" for e.g. this use:

int main()
{
  uint8_t rawBytes[]{ 1u, 2u, 3u, 4u };
  auto x{ parse<data_types::single_bytes>(rawBytes) };
}

How can we define a dependent type that will fail if no option is valid, but only if no option is valid?


Solution

  • You might delay instantiation with std::type_identity and use extra ::type std::conditional_t<>::type

    using type =
        typename std::conditional_t<
          expected_type == data_types::single_bytes, std::type_identity<uint8_t>,
          std::conditional_t<expected_type == data_types::four_byte_integrals, std::type_identity<int32_t>,
          std::enable_if<expected_type == data_types::four_byte_floats, float>>
    
    //               ^^^^ no _t here
          >::type; // extra `::type` here, for the type inside conditional_t