Как создать функцию constexpr, которая возвращает тип (который будет использоваться в параметре шаблона)

Я ищу способ создания класса с типом параметра шаблона на основе номера параметра шаблона.

То, что я пытаюсь сделать, это примерно так:

template<size_t n>
constexpr auto type_from_size() {
    if(n < 256) {
        return uint8_t;
    } else {
        return uint16_t;
    }
}

template<size_t n>
class X {
    type_from_size<n>() t;
}

X<500> x;
x.t = 500;

Итак, в приведенном выше коде функция constexpr type_from_size() получит число 500 и вернет тип uint16_t, и это будет тип члена X.t.

Я знаю, что это явно ужасный код, но возможно ли это с помощью шаблонов?

Ответ 1

Функция не может вернуть тип. Вы должны использовать шаблон.

Для выбора между двумя типами достаточно встроенного std::conditional.

#include <type_traits>
#include <cstdint>

template <size_t n>
using type_from_size = typename std::conditional<(n < 256), uint8_t, uint16_t>::type;
// ^ if `n < 256`, the ::type member will be typedef'ed to `uint8_t`.
//                 otherwise, it will alias to `uint16_t`.
//   we then give a convenient name to it with `using`.

template <size_t n>
struct X {
    type_from_size<n> t;
    // ^ use the template
};

Если вам нужно поддерживать более двух значений, вы можете изменить несколько conditional вместе как цепочка if/else if/else, но ОН МОЙ ГЛАЗА

template <size_t n>
using type_from_size =
    typename std::conditional<(n <= 0xff), uint8_t,
        typename std::conditional<(n <= 0xffff), uint16_t,
            typename std::conditional<(n <= 0xffffffff), uint32_t,
                uint64_t
            >::type
        >::type
    >::type;

Вы также можете использовать специализацию вместе с std::enable_if (SFINAE), чтобы сделать ее более "низкоуровневой":

template <size_t n, typename = void>
struct type_from_size_impl;
// Declare a "size_t -> type" function.
//  - the `size_t n` is the input
//  - the `typename = void` is a placeholder
//    allowing us to insert the `std::enable_if` condition.

template <size_t n>
struct type_from_size_impl<n, typename std::enable_if<(n <= 0xff)>::type> {
    using type = uint8_t;
};
// We add a partial specialization
//  - in `std::enable_if<c>::type`, if `c` is true, `::type` will be typedef'ed to `void`
//  - otherwise, `::type` will not be defined.
//  - if `::type` is not defined, substitution failed,
//    meaning we will not select this specialization

template <size_t n>
struct type_from_size_impl<n, typename std::enable_if<(n > 0xff && n <= 0xffff)>::type> {
    using type = uint16_t;
};

template <size_t n>
struct type_from_size_impl<n, typename std::enable_if<(n > 0xffff && n <= 0xffffffff)>::type> {
    using type = uint32_t;
};

template <size_t n>
struct type_from_size_impl<n, typename std::enable_if<(n > 0xffffffff)>::type> {
    using type = uint64_t;
};

template <size_t n>
using type_from_size = typename type_from_size_impl<n>::type;
// Here we want to find a specialization of `type_from_size_impl<n>`
// All 4 specializations will be tried.
// If only one specialization works, we will use that one
// (Which is why we need to ensure the ranges are not overlapping
//  otherwise the compiler will complain)
// Then we take the `::type` out the complete this "type-level function".

Ответ 2

Пусть просто перевалить. Начните с выбора:

template <int I> struct choice : choice<I + 1> { };
template <> struct choice<10> { };

struct otherwise { otherwise(...) { } };

Затем создайте каскадную серию перегрузок, возвращающих тип. Выбор гарантирует, что наименьший тип будет выбран первым, без необходимости писать двухсторонние диапазоны для всех промежуточных целых типов:

template <class T> struct tag { using type = T; }
template <size_t N> using size_t_ = std::integral_constant<size_t, N>;

template <size_t N, class = std::enable_if_t<(N < (1ULL << 8))>>
constexpr tag<uint8_t> tag_from_size(size_t_<N>, choice<0> ) { return {}; }

template <size_t N, class = std::enable_if_t<(N < (1ULL << 16))>>
constexpr tag<uint16_t> tag_from_size(size_t_<N>, choice<1> ) { return {}; 

template <size_t N, class = std::enable_if_t<(N < (1ULL << 32))>>
constexpr tag<uint32_t> tag_from_size(size_t_<N>, choice<2> ) { return {}; }

template <size_t N>
constexpr tag<uint64_t> tag_from_size(size_t_<N>, otherwise) { return {}; }

И тогда вы можете написать верхний уровень, который отправляет:

template <size_t N>
using type_from_size_t = typename decltype(tag_from_size(size_t_<N>{}, choice<0>{}))::type;

И используйте его:

template <size_t N>
class X {
    type_from_size_t<N> t;
};

Ответ 3

Конечно. Здесь более гибкий способ сделать это, вы можете добавить столько диапазонов, сколько хотите, если они не перекрываются.

template <std::size_t N, class = void>
struct TypeForSize_;

template <std::size_t N>
struct TypeForSize_<N, std::enable_if_t<
    (N <= 255)
>> { using type = std::uint8_t; };

template <std::size_t N>
struct TypeForSize_<N, std::enable_if_t<
    (N > 255 && N <= 65535)
>> { using type = std::uint16_t; };

template <std::size_t N>
using TypeForSize = typename TypeForSize_<N>::type;

Использование размера, для которого не был определен тип, приведет к ошибке времени компиляции.

Ответ 4

Сначала напишите static_if<bool>( A, B ).

Затем напишите template<class T> struct tag_type{using type=T;}; и поддерживающий код.

template<size_t n>
constexpr auto type_from_size() {
  return static_if< (n<256) >( 
    tag<std::uint8_t>,
    tag<std::uint16_t>
  );
}

теперь возвращает другой тип тега, основанный на значении n.

Для использования:

template<size_t n>
class X {
  typename decltype(type_from_size<n>())::type t;
}

или напишите быстрый псевдоним:

template<size_t n> type_from_size_t = typename decltype(type_from_size<n>())::type;

Вот код для tag_type и static_if:

template<class T>struct tag_type{using type=T;};
template<class T>constexpr tag_type<T> tag{};

template<bool b>using bool_t=std::integral_constant<bool, b>;

template<class T, class F>
constexpr T static_if( bool_t<true>, T t, F f ) {
  return t;
}
template<class T, class F>
constexpr F static_if( bool_t<false>, T t, F f ) {
  return f;
}
template<bool b, class T, class F>
constexpr auto static_if( T t, F f ) {
  return static_if( bool_t<b>, t, f );
}
template<bool b, class T>
constexpr auto static_if( T t ) {
  return static_if<b>( t, [](auto&&...){} );
}

и сделано.

Заметьте, что мы также можем сделать static_case.:)