Есть хороший способ реализовать условный тип с регистром сбоя по умолчанию? - PullRequest
13 голосов
/ 08 июля 2019

Для реализации условного типа мне очень нравится 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::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 }. Urgh.


Мой текущий обходной путь к этому довольно неуклюже, представляя структуру и 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 на самом деле является доходностью от ветви. Есть ли читаемое, короткое решение?

Ответы [ 2 ]

18 голосов
/ 08 июля 2019

Вы можете решить эту проблему, добавив уровень косвенности, чтобы результат самого внешнего 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<<i>condition</i>, uint64_t>, который всегда является допустимым типом, и вы получите ошибку только в том случае, если эта ветвь фактически взята и enable_if<false, uint64_t>::type необходима. Когда берется одна из более ранних веток, вы в конечном итоге используете identity<uintNN_t>::type для одного из целочисленных типов меньшего размера, и не имеет значения, что enable_if<false, uint64_t> не имеет вложенного типа (потому что вы его не используете).

6 голосов
/ 08 июля 2019

Просто для удовольствия ... как насчет использования 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;

вызывает ошибку компиляции.

- EDIT - По предложению Джонатана Уэйкли, если вы можете использовать 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>>;
...