2

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

Piotr Sulecki
  • 103
  • 1
  • 9

4 Answers4

6

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; };
YSC
  • 38,212
  • 9
  • 96
  • 149
  • @UKMonkey just to be sure, it is _not_ sarcastic, right? – YSC Jan 09 '18 at 16:55
  • Nice, but he has edited the terms in the question. He's looking for something that works for any value between 0 and 64 (inclusive). – oisyn Jan 09 '18 at 17:15
  • A nice solution, but it only works for the specified bit sizes, plus you omitted a `typename`. The correct last name should be `struct MyClass { using int_type = typename uint::type; };`. – Piotr Sulecki Jan 09 '18 at 17:19
2

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;
};
super
  • 12,335
  • 2
  • 19
  • 29
0

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
R Sahu
  • 204,454
  • 14
  • 159
  • 270
0

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;
};
Giovanni Cerretani
  • 1,693
  • 1
  • 16
  • 30