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?
A constexpr function is a function that can be invoked within a constant expression. A constexpr function must satisfy the following conditions: It is not virtual. Its return type is a literal type.
We allow annotating a function parameter with constexpr with the same meaning as a variable declaration: must be initialized with a constant expression.
Static specifies the lifetime of the variable. A static constexpr variable has to be set at compilation, because its lifetime is the the whole program. Without the static keyword, the compiler isn't bound to set the value at compilation, and could decide to set it later.
A call to a constexpr function produces the same result as a call to an equivalent non- constexpr function , except that a call to a constexpr function can appear in a constant expression. The main function cannot be declared with the constexpr specifier.
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".
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With