Reputation: 7915
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 bitsstd::uint16_t
2 bytes 16 bitsstd::uint32_t
4 bytes 32 bitsstd::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
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