Reputation: 1477
I'm looking for some way to create a class, with a template parameter type, based on a template parameter number.
What I'm trying to do is something like this:
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;
So, in the code above, the constexpr
function type_from_size()
would receive the number 500 and would return the type uint16_t
, and this would be the type of the member X.t
.
I know this is obviously terrible code, but is this possible using templates?
Upvotes: 28
Views: 5446
Reputation: 537
constexpr
function cannot return a type directly but a simple wrapper will work.
template <typename T>
struct TypeWrapper {
using type = T;
};
template <size_t n>
constexpr auto type_from_size_func() {
if constexpr (n <= 0xff) {
return TypeWrapper<uint8_t>{};
} else {
if constexpr (n <= 0xffff) {
return TypeWrapper<uint16_t>{};
} else {
return TypeWrapper<uint32_t>{};
}
}
}
template <size_t N>
using type_from_size = typename decltype(type_from_size_func<N>())::type;
usage
type_from_size<123> x; /*uint8_t*/
type_from_size<1234> y; /*uint16_t*/
type_from_size<12345678> z; /*uint32_t*/
Upvotes: 11
Reputation: 303337
Let's just go overkill. Start with a chooser:
template <int I> struct choice : choice<I + 1> { };
template <> struct choice<10> { };
struct otherwise { otherwise(...) { } };
Then create a cascading series of overloads that return a type. The choice ensures that the smallest type will be selected first, without having to write two-sided ranges for all the intermediate integer types:
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 {}; }
And then you can write the top level one that dispatches:
template <size_t N>
using type_from_size_t = typename decltype(tag_from_size(size_t_<N>{}, choice<0>{}))::type;
And use it:
template <size_t N>
class X {
type_from_size_t<N> t;
};
Upvotes: 5
Reputation: 275730
First write static_if<bool>( A, B )
.
Next, write template<class T> struct tag_type{using type=T;};
and supporting code.
template<size_t n>
constexpr auto type_from_size() {
return static_if< (n<256) >(
tag<std::uint8_t>,
tag<std::uint16_t>
);
}
now returns a different tag type based on the value of n
.
To use:
template<size_t n>
class X {
typename decltype(type_from_size<n>())::type t;
}
or write a quick alias:
template<size_t n> type_from_size_t = typename decltype(type_from_size<n>())::type;
Here is the code for tag_type
and 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&&...){} );
}
and done.
Note we can also do static_case
. :)
Upvotes: 3
Reputation: 523474
A function cannot return a type. You should use a template.
For a selection between only two types, the built-in std::conditional
is sufficient.
#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
};
If you need to support more than two values, you can change multiple conditional
together like an if/else if/else
chain, but OH MY EYES
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;
You could also use specialization together with std::enable_if
(SFINAE) to make it more "low-level":
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".
Upvotes: 28
Reputation: 63144
Definitely. Here's a more flexible way of doing it, you can add as many ranges as you wish as long as they don't overlap.
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;
Using a size for which no type has been defined will result in a compile-time error.
Upvotes: 6