Francis Cugler
Francis Cugler

Reputation: 7915

Initializing a class template's member based on different conditions

I have a class template that has two constructors, one takes a unsigned type and the other takes a constant reference of its self, both constructors have the same behavior in how it constructs the class.

There are 3 cases in how this class can be constructed and this is determined on the sizes of the type it is and the type being passed in. I also would like the members to be initialized by the class constructors' initializer lists.

The class looks something like this:

template<typename T>
struct Foo {
    T data;

    template<typename P>
    Foo( const P val, const unsigned char c ) :
    data { static_cast<T>( val /* some operation done to val based on each case*/ ) } {
        assert( /* based on cases above,
                and each case has its own conditions to assert */ ); 
    }

    template<typename P>
    Foo( const Foo<P>& f, const unsigned char c ) :
    data{ static_cast<T>( Foo.val /* some operation done to val based on each case*/ ) } {
        assert( /* based on cases above, 
                and each case has its own conditions to assert */ );
};

The 3 cases are as follows:

case 1: if( sizeof(P) > sizeof(T) )
           construct Foo<T> with these conditions,
           initialize member data in this specific way
           and assert accordingly
case 2: if( sizeof(T) > sizeof(P) )
           construct Foo<T> with these conditions,
           initialize member data in this specific way
           and assert accordingly
case 3: if ( sizeof(T) == sizeof(P) )
           construct Foo<T> with these conditions,
           initialize member data in this specific way
           and assert accordingly

Is there a simple way to do this? Remember that the member is being initialized via the constructors' initializer list, but the data member will be initialized in a different manner all depending on the 3 cases above.

I don't know how I would be able to approach this. All the information here should be available at compile time, so I don't see why there should be an issues, but I don't know how to setup the constructors to have this functionality. Furthermore can this even be done?


EDIT

A little bit of background on types T & P: both T & P are any of the following:

Types: Size in Bytes: Size in bits:

  • std::uint8_t 1 byte 8 bits
  • std::uint16_t 2 bytes 16 bits
  • std::uint32_t 4 bytes 32 bits
  • std::uint64_t 8 bytes 64 bits

Once we have our 3 conditions defined based on the comparison of the two sizes, the assertions work as follows: the unsigned char passed in is also a part of the conditions within the assertions and it has a range of values that it can be. Here is a table for that: The unsigned char I'll represent with idx.

using u8  = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;

if ( sizeof(P) > sizeof(T) )
   // comparision formula:
   // idx <= (((sizeof(P) / sizeof(T)) - 1)
   // Foo<T = u8> From: Foo<P = u16> idx[0,1], Foo<P =u32> idx[0,3], Foo<P = u64> idx[0,7]
   // Foo<T = u16> From: Foo<P = u32> idx[0,1], Foo<P = u64> idx[0,3]
   // Foo<T = u32> From: Foo<P = u64> idx[0,1]

if ( sizeof(T) > sizeof(P) )
   // comparision formula:
   // idx <= (((sizeof(T) / sizeof(P)) - 1)
   // Foo<T = u16> From: Foo<P = u8> idx[0,1]
   // Foo<T = u32> From: Foo<P = u8> idx[0,4], Foo<P = u16> idx[0,1]
   // Foo<T = u64> From: Foo<P = u8> idx[0,7], Foo<P = u16> idx[0,3], Foo<P = u32> idx[0,1]

if ( sizeof(P) == sizeof(T) ) {
   // no conditional checks
   // no assertion
   // in this case idx is not used and is not a part of any calculation
   // the two sizes match so it's just a normal initialization.

Upvotes: 1

Views: 61

Answers (1)

Brian Bi
Brian Bi

Reputation: 119437

This is a situation where if constexpr cannot be used and you have to fall back on SFINAE. First, delegate the construction from the Foo<P> constructor to the P constructor:

template<typename P>
Foo(const Foo<P>& f, const unsigned char c) : Foo(f.val, c) {}

The P constructor just needs to have three alternatives, exactly one of which is enabled depending on the size of P:

template<typename P, std::enable_if_t<(sizeof(P) > sizeof(T))>* = nullptr>
Foo(P val, const unsigned char c) : data(...) {}

template<typename P, std::enable_if_t<sizeof(P) == sizeof(T)>* = nullptr>
Foo(P val, const unsigned char c) : data(...) {}

template<typename P, std::enable_if_t<(sizeof(P) < sizeof(T))>* = nullptr>
Foo(P val, const unsigned char c) : data(...) {}

You could also add an extra layer of delegation if you want the P constructor to have the same assertions regardless of the size of P.

Upvotes: 3

Related Questions