问题
I want to map a number to a type. For this example I'll make a function that maps a sizeof() result onto a signed primitive type.
I am wondering if there is a better way of doing what I did below in modern C++, which is to take a templated value and convert it into a type. Right now this works in converting a size to a known type, but I can't seem to find anything in the standard library that does what I want. Have I missed something?
If not, is there a better way of doing this or cleaning up this code? For example if somehow in the future we end up having 128 bit types, this wouldn't support that.
#include <iostream>
#include <type_traits>
template <size_t S>
static constexpr auto sizeToType() {
static_assert(S == 1 or S == 2 or S == 4 or S == 8, "Bad type size");
if constexpr (S == 1)
return int8_t{};
else if constexpr (S == 2)
return int16_t{};
else if constexpr (S == 4)
return int32_t{};
else
return int64_t{};
}
int main() {
using MY_TYPE = decltype(sizeToType<2>());
MY_TYPE myType = MY_TYPE(0xFFFFFFFFFFFFFFFEUL);
std::cout << sizeof(MY_TYPE) << " bytes" << std::endl;
std::cout << "MY_TYPE(0xFFFFFFFFFFFFFFFEUL) = " << myType << std::endl;
}
Output (as expected):
2 bytes
MY_TYPE(0xFFFFFFFFFFFFFFFEUL) = -2
回答1:
I would not use C++17 if constexpr
for this, instead I'd use template specialization, as it looks more declarative to me.
Something along the following lines:
template<size_t S> struct SizeToType {static_assert(S != S, "Wrong size"); };
template<> struct SizeToType<1> { using type = uint8_t; };
template<> struct SizeToType<2> { using type = uint16_t; };
template<> struct SizeToType<4> { using type = uint32_t; };
template<> struct SizeToType<8> { using type = uint64_t; };
template<size_t S>
using SizeToToTypeT = typename SizeToType<S>::type;
Adding more types would be just adding more specializations (one-liners) here.
回答2:
Not a great solution... but just for fun...
I propose a type traits (with helper and using to select the internal type) that, given a number (the number of bytes) and a list of types select the first type of the list with sizeof()
greater or equal (or nothing if there is no such type)
template <std::size_t, typename, typename = std::true_type>
struct sizeTypeH;
template <std::size_t Dim>
struct sizeTypeH<Dim, std::tuple<>, std::true_type>
{ }; // no type found
template <std::size_t Dim, typename T0, typename ... Ts>
struct sizeTypeH<Dim, std::tuple<T0, Ts...>,
std::integral_constant<bool, (sizeof(T0) >= Dim)>>
{ using type = T0; };
template <std::size_t Dim, typename T0, typename ... Ts>
struct sizeTypeH<Dim, std::tuple<T0, Ts...>,
std::integral_constant<bool, (sizeof(T0) < Dim)>>
: public sizeTypeH<Dim, std::tuple<Ts...>>
{ };
template <std::size_t Dim, typename ... Ts>
struct sizeType : public sizeTypeH<Dim, std::tuple<Ts...>>
{ };
template <std::size_t Dim, typename ... Ts>
using sizeType_t = typename sizeType<Dim, Ts...>::type;
Now another using
that, receiving a integer, call this type traits with the ordered list of intX_t
types
template <std::size_t Dim>
using intType_t = sizeType_t<Dim, std::int8_t, std::int16_t, std::int32_t,
std::int64_t>;
In my platform I have that the following static_assert()
are verified.
using t8 = intType_t<1u>;
using t16 = intType_t<2u>;
using t32 = intType_t<4u>;
using t64 = intType_t<8u>;
static_assert( std::is_same<t8, std::int8_t>{}, "!" );
static_assert( std::is_same<t16, std::int16_t>{}, "!" );
static_assert( std::is_same<t32, std::int32_t>{}, "!" );
static_assert( std::is_same<t64, std::int64_t>{}, "!" );
If, tomorrow, is introduced a int128_t
, you can simply add it in intType_t
using
definition.
Attention, please: there is no guaranties that the preceding static_assert()
are satisfied.
First of all because the standard guaranties that a byte is at least of 8 bits; but can be more that 8 bits. If a byte is of 16 bits, from
using t16 = intType_t<2u>;
you get a type of 32 bits.
Second because intX_t
types are optionals.
来源:https://stackoverflow.com/questions/53488151/mapping-an-integral-template-parameter-value-onto-a-primitive-type