Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Select an integer type based on template integer parameter

Tags:

c++

templates

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?

like image 338
Edgard Lima Avatar asked Jul 10 '15 06:07

Edgard Lima


3 Answers

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).

like image 62
Bathsheba Avatar answered Oct 16 '22 18:10

Bathsheba


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; }; 
like image 34
manlio Avatar answered Oct 16 '22 17:10

manlio


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_;
};
like image 21
Barry Avatar answered Oct 16 '22 16:10

Barry