'How can I specialize a C++ template for multiple ranges of integer values?

I'm trying to create a template class for handling bit streams. I'd like to have an underlying integer type declared in the template that would resolve to either uint8_t, uint16_t, uint32_t or uint64_t, depending on the template argument (an int, number of bits). I found the two answers regarding this subject (How can I specialize a C++ template for a range of integer values? and Integer range based template specialisation) and implemented the following code:

template<int BITS>
class MyClass {
   typedef typename
      std::conditional< BITS <= 8,  uint8_t,
      std::conditional< BITS <= 16, uint16_t,
      std::conditional< BITS <= 32, uint32_t, uint64_t > > >::type
         int_type;
    ...
}

In my program, I instantiate MyClass<32>, but when compiling this, I'm getting the following error:

no known conversion for argument 1 from ‘uint32_t {aka unsigned int}’ to ‘MyClass<32>::int_type {aka std::conditional<false, short unsigned int, std::conditional<true, unsigned int, long unsigned int> >}’

If I'm instantiating MyClass<8> instead, everything works. So it looks like only the first level of std::conditional is actually expanded.

Any idea how to do this correctly?

Edit: I haven't stated this before, but I'm looking for a solution which would also work for any bit size instantiation (as long as it's 64 bits at most). So I'd like MyClass<27> to work as well (selecting uint32_t).



Solution 1:[1]

To answer your edit and make your original code work.

template<int BITS>
class MyClass {
   using int_type =
      typename std::conditional< BITS <= 8,  uint8_t,
      typename std::conditional< BITS <= 16, uint16_t,
      typename std::conditional< BITS <= 32, uint32_t, uint64_t >::type >::type >::type;
   public:
   int_type i;
};

Solution 2:[2]

The simplest the better:

template<unsigned nbits> struct uint {};

template<> struct uint<8> { using type = uint8_t; };
template<> struct uint<16> { using type = uint16_t; };
template<> struct uint<32> { using type = uint32_t; };
template<> struct uint<64> { using type = uint64_t; };

template<int nbits>
struct MyClass { using int_type = typename uint<(nbits/8)*8>::type; };

Solution 3:[3]

The solution to the problem is already provided in the answer by @super.

It was instructive for me to understand the error message.

You have defined int_type as:

typedef typename
  std::conditional< BITS <= 8,  uint8_t,
  std::conditional< BITS <= 16, uint16_t,
  std::conditional< BITS <= 32, uint32_t, uint64_t > > >::type
     int_type;

It works correctly for BITS <= 8 since the

  std::conditional< BITS <= 16, uint16_t,
  std::conditional< BITS <= 32, uint32_t, uint64_t > >

part is essentially ignored.

Let's say you use MyClass<16>. Then, the uint8_t is ignored. What you have for int_type is:

  std::conditional< BITS <= 16, uint16_t,
  std::conditional< BITS <= 32, uint32_t, uint64_t > >

That can be simplified to:

  std::conditional< true, uint16_t,
  std::conditional< true, uint32_t, uint64_t > >

Unfortunately, that is the type you get without using std::conditional<...>::type.

Let's say you use MyClass<32>. Then what you have for int_type is:

  std::conditional< BITS <= 16, uint16_t,
  std::conditional< BITS <= 32, uint32_t, uint64_t > >

That can be simplified to:

  std::conditional< false, uint16_t,
  std::conditional< true, uint32_t, uint64_t > >

That is the type you get.

You can get some idea of those types by printing name of the corresponding type_info objects.

Sample program:

#include <iostream>
#include <typeinfo>
#include <type_traits>
#include <cstdint>

template<int BITS>
struct MyClass
{
   typedef typename
      std::conditional< BITS <= 8,  uint8_t,
      std::conditional< BITS <= 16, uint16_t,
      std::conditional< BITS <= 32, uint32_t, uint64_t > > >::type
         int_type;
};

int main()
{
   typename MyClass<8>::int_type a;
   std::cout << typeid(a).name() << std::endl;

   typename MyClass<16>::int_type b;
   std::cout << typeid(b).name() << std::endl;

   typename MyClass<32>::int_type c;
   std::cout << typeid(c).name() << std::endl;

   typename MyClass<60>::int_type d;
   std::cout << typeid(d).name() << std::endl;
}

Output, using g++ 5.4.0:

h
St11conditionalILb1EtS_ILb1EjmEE
St11conditionalILb0EtS_ILb1EjmEE
St11conditionalILb0EtS_ILb0EjmEE

Solution 4:[4]

You should have a look at Boost.Integer: its Integer Type Selection does exactly what you are looking for. In your case you should use boost::uint_t<N>::least:

#include <boost/integer.hpp>

template<int BITS>
class MyClass {
   using int_type = typename boost::uint_t<BITS>::least;
};

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 super
Solution 2
Solution 3 R Sahu
Solution 4 Giovanni Cerretani