I would like to create a class template which takes an unsigned integer parameter and has a member u_
whose type is the smallest unsigned integer type that will hold the integer parameter.
So:
template <uint64_t k>
class A {
??? u_;
};
For A<0>
, u_
should be of type uint8_t
. Same for A<255>
. For A<256>
, u_
should be of type uint16_t
, etc.
How would you implement this?
This piece of metaprogramming trickery achieves it:
template<unsigned int Y> struct typed
{
typedef typename typed<(Y & (Y - 1)) == 0 ? Y / 2 : (Y & (Y - 1))>::type type;
};
template<> struct typed<0>
{
typedef std::uint8_t type;
};
template<> struct typed<256>
{
typedef std::uint16_t type;
};
template<> struct typed<65536>
{
typedef std::uint32_t type;
};
/* ToDo - add more specialisations as necessary*/
template<unsigned k> class A
{
public:
unsigned static const k_ = k; /*constexpr if your compiler supports it*/
typename typed<k>::type u_;
};
The usage is exactly in the question.
The unspecialised template version takes the previous type. typed<0>
blocks the static recursion. The other specialisations act as anchoring points for the appropriate types.
The compile-time evaluable (Y & (Y - 1)) == 0 ? Y / 2 : (Y & (Y - 1))
reduces the number of instantiations by removing the rightmost bit of Y
until a power of 2 is reached, and then divides by 2 subsequently to that. (Acknowledge @Jarod42).
With C++11 you can use std::conditional:
#include <cassert>
#include <cstdint>
#include <limits>
#include <type_traits>
template<std::uint32_t K>
class A
{
public:
decltype(K) k_ = K;
typename std::conditional<K <= UINT8_MAX,
std::uint8_t,
typename std::conditional<K <= UINT16_MAX,
std::uint16_t,
std::uint32_t>::type>::type u_;
};
int main()
{
A<100> small;
A<1000> medium;
A<100000> large;
assert( (std::is_same<std::uint8_t, decltype(small.u_)>::value) );
assert( (std::is_same<std::uint16_t, decltype(medium.u_)>::value) );
assert( (std::is_same<std::uint32_t, decltype(large.u_)>::value) );
}
This assumes that:
uint8_t
in template<uint8_t k, typename >
is just an oversight or, as pointed out in Aaron McDaid's comment, the example doesn't work. I changed uint8_t
to uint32_t
(the example can be smoothly extended to uint64_t
);int k_ = k;
is a in-class member initialization. For a constant definition you can use enum {k_ = K};
u_
in typename u_;
is a data member. If it's a type definition you can easily change the example using a typedef
or a type-alias (C++11).If you cannot use C++11 there's boost::conditional or you can write your own version:
template<bool, class T, class F>
struct conditional { typedef T type; };
template<class T, class F>
struct conditional<false, T, F> { typedef F type; };
If what we want is: given a uint64_t
template parameter, give the smallest unsigned type that is capable of representing it, then what we really want is just a simple iteration at compile time.
namespace details {
template <typename T>
struct tag {
using type = T;
};
// base case: just fail
template <uint64_t V, typename... >
struct min_unsigned_type;
// recursive case: check using numeric_limits
template <uint64_t V, typename T, typename... Ts>
struct min_unsigned_type<V, T, Ts...>
: std::conditional_t<(V <= std::numeric_limits<T>::max()),
tag<T>,
min_unsigned_type<V, Ts...>>
{ };
}
Then just an alias to wrap things together:
template <uint64_t V>
using min_unsigned_type =
typename details::min_unsigned_type<V,
uint8_t, uint16_t, uint32_t, uint64_t>::type;
This has the added advantage of being able to easily specify how far you want to go, or even being able to add larger unsigned types if that's something you find necessary.
And finally your class:
template <uint64_t V>
struct A {
static constexpr uint64_t k_ = V;
min_unsigned_type<V> u_;
};
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