Search code examples
c++templatesc++17template-specializationtemplate-argument-deduction

Automating a class template's size argument based on its type argument


While working with class templates, is there a way to specify a particular size by inferring from its type? Here is a pseudo code example to represent what I'm trying to ask.

#include <bitset>
#include <cstdint>

namespace xxx {

#define BYTE  8
#define WORD  16
#define DWORD 32
#define QWORD 64

    typedef std::uint8_t   u8;
    typedef std::uint16_t u16;
    typedef std::uint32_t u32;
    typedef std::uint64_t u64;

    template<typename Type, u16 BitCount>
    struct Register {
    };
}

I don't know if I can use partial specialization or full specialization, and I don't know how to go about doing this. What I would like to do is the following:

  • if Type == u8 then BitCount automatically == BYTE or 8
  • if Type == u16 then BitCount automatically == WORD or 16
  • if Type == u32 then BitCount automatically == DWORD or 32
  • if Type == u64 then BitCount automatically == QWORD or 64

The reasoning for this is that when it comes to the class above with its members which I haven't shown yet but will show here is that one of its members is a std::bitset

template<typename Type, u16 BitCount>
struct Register {
   Type value;
   std::bitset<BitCount> 
};

Yes I know that I can instantiate them like this:

void someFunc() {
    Register<u8, 8> r8;
    Register<u16, 16> r16;
}

But I would like to be able to possibly specialize them so that you don't have to pass in any argument types, I was hoping that they could be deduced by the parameter types that are passed in or, if one just passes the type, then the size part is automatic. This would be preferred

void someFunc() {
    Register<u8> r8;
} 

// the class would automatically use `8` for its `std::bitset<8>` member.

There will always be a one to one correspondence of its underlying type and the size in bits.

This kind of instantiation is invalid:

Register<u32, 64> reg; // invalid type u32 can only be 32...

Is there any known way of doing this through specialization, inheritance, polymorphism, etc.?


Solution

  • It sounds like you don't actually want a second template parameter. You just want one template parameter, and just determine the number of bits directly from it:

    template <typename Type>
    struct Register {
        static constexpr auto Bits = sizeof(Type) * CHAR_BIT;
        // ...
    };
    

    Or if you want to generalize that logic somewhat:

    template <typename> struct BitCount;
    template <> struct BitCount<uint8_t>  : integral_constant<size_t, 8> {};
    template <> struct BitCount<uint16_t> : integral_constant<size_t, 16> {};
    // ...
    
    template <typename Type>
    struct Register {
        static constexpr auto Bits = BitCount<T>::value;
        // ...
    };