Applying a method generically to initialize member through a class template's constructor using SFINAE with constructor delegation

1

I have this class template that is using SFINAE with constructor delegation. There are 3 cases to determine which version of the constructor(s) that will be called.

Overall structure of class:

  • In the first case it is constructing a smaller size from a larger size and can extract a byte, word, or dword from a word, dword or qword by the index value

  • In the second case it is constructing a larger size from a smaller size and can set a byte word or dword into word, dword or qword at that index location.

  • In the third case (default) case it is a 1 to 1 mapping so no calculations nor assertions need to be performed, just save the contents, and the index parameter if passed will have no effect.


Register.h

#pragma once

#include <assert.h>
#include <bitset>
#include <cstdint>
#include <iostream>
#include <iomanip>
#include <limits>
#include <type_traits>

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

    template<typename T>
    struct Register {
        T data;
        T value;
        std::bitset<sizeof(T)* CHAR_BIT> bits;

        Register() : data{ 0 }, value{ 0 }, bits{ 0 } {}

        template<typename P, std::enable_if_t<(sizeof(P) > sizeof(T))>* = nullptr>
        Register(const P val, const u8 idx = 0) :
            data{ static_cast<T>((val >> std::size(bits) * idx) &
                  std::numeric_limits<std::make_unsigned_t<T>>::max()) },
            value{ data },
            bits{ data }
        {

            constexpr u16 sizeT = sizeof(T);
            constexpr u16 sizeP = sizeof(P);
            assert((idx >= 0) && (idx <= ((sizeP / sizeT) - 1)) );
        }    

        template<typename P, std::enable_if_t<(sizeof(P) < sizeof(T))>* = nullptr>
        Register(const P val, const u8 idx = 0) :
            data{ /*static_cast<T>((val >> std::size(bits) * idx) &
                  std::numeric_limits<std::make_unsigned_t<T>>::max()) },*/
                static_cast<T>(val)
                },
            value{ data },
            bits{ data }
        {
            constexpr u16 sizeT = sizeof(T);
            constexpr u16 sizeP = sizeof(P);
            assert((idx >= 0) && (idx <= ((sizeT / sizeP) - 1)) );
        }    

        template<typename P, std::enable_if_t<(sizeof(P) == sizeof(T))>* = nullptr>
        Register(const P val, const u8 idx = 0) :
                  // shouldn't need the static cast but I'll leave it here for now
            data{ static_cast<T>( val ) }, value{ data }, bits{ data }
        {}

        template<typename P>
        Register(const Register<P>& reg, const u8 idx = 0) : Register(reg.data, idx) {}

    };

    using Reg8  = Register<u8>;
    using Reg16 = Register<u16>;
    using Reg32 = Register<u32>;
    using Reg64 = Register<u64>;

    template<typename T>
    std::ostream& operator<<(std::ostream& os, const Register<T>& r) {
        return os << "Reg" << std::size(r.bits) << '(' << r.data << ")\nhex: 0x"
            << std::uppercase << std::setfill('0') << std::setw(sizeof(T) * 2) << std::hex
            << r.data << std::dec << "\nbin: "
            << r.bits << "\n\n";
    }

    template<>
    std::ostream& operator<<<u8>(std::ostream& os, const Register<u8>& r) {
        return os << "Reg" << std::size(r.bits) << '(' << +r.data << ")\nhex: 0x"
            << std::uppercase << std::setfill('0') << std::setw(sizeof(u8) * 2) << std::hex
            << +r.data << std::dec << "\nbin: "
            << r.bits << "\n\n";
    }
} // namespace

And if we look at the first case where sizeof(P) > sizeof(T) where we are using the class's initializer list to initialize it's member data The following formula is being done to data:

data{ static_cast<T>((val >> std::size(bits) * idx) &
                      std::numeric_limits<std::make_unsigned_t<T>>::max()) }

And in the second case where sizeof(P) < sizeof(T) it is currently commented out.

data{ /*static_cast<T>((val >> std::size(bits) * idx) &
      std::numeric_limits<std::make_unsigned_t<T>>::max()) },*/
    static_cast<T>(val)
    }

What I would like to do is something similar above but I want to generically apply this method to initialize data in this case:

void insertByte(unsigned char a, unsigned int& value, unsigned idx) {
    if (idx > 3)
        return;

    // clear the value at position idx
    value &= ~(0xFF << (idx * 8));

    unsigned int tmp = a;
    tmp = (tmp << (idx * 8));

    value |= tmp;
}

The parameters in the function above a and value will be the template types: T and P within my class. The if statement will be handled by the assertion.

This may also help to understand the function above:

unsigned a = (the_int & 0x00ffffff) | (the_byte << 24);  // set high-order byte: bits 24-31
unsigned b = (the_int & 0xff00ffff) | (the_byte << 16);  // next byte, bits 16-23
unsigned c = (the_int & 0xffff00ff) | (the_byte << 8);   // next byte, bits 8-15
unsigned d = (the_int & 0xffffff00) | (the_byte);        // low-order byte: bits 0-7

Any thoughts on how I could convert the above function to fit my template in order to initialize data with the correct values? It is basically the reverse of the first case constructor.



Edit



Based on a comment from user: Davis Herring I'm going to illustrate the concept of my second case constructor:

Reg8 r8{ 0xAA };

Reg32 r32a{ r8, 0 };
Reg32 r32b{ r8, 1 };
Reg32 r32c{ r8, 2 };
Reg32 r32d{ r8, 3 };
// Reg32 r32{ r8, 4 }; // assertion failure

// binary output in hex notation:
r8   = 0xAA
r32a = 0x000000AA
r32b = 0x0000AA00
r32c = 0x00AA0000
r32d = 0xAA000000

// Another example
Reg16 r16{ 0xABCD };

Reg32 r32a{ r16, 0 };
Reg32 r32b{ r16, 1 };
// Reg32 r32c{ r16, 2 }; // assertion failure 

Reg64 r64a_0{ r32a, 0 };
Reg64 r64a_1{ r32a, 1 };
// Reg64 r64a_2{ r32a, 2 }; // assertion failure

Reg64 r64b_0{ r32b, 0 };
Reg64 r64b_1{ r32b, 1 };
// Reg64 r64b_2{ r32b, 2 }; // assertion failure

Reg64 r64c_0{ r16, 0 };
Reg64 r64c_1{ r16, 1 };
Reg64 r64c_2{ r16, 2 };
Reg64 r64c_3{ r16, 3 };
// Reg64 r64c_4{ r16, 4 }; // assertion failure

// binary output in hex notation:
r16    = 0xABCD
r32a   = 0x0000ABCD
r32b   = 0xABCD0000
r64a_0 = 0x000000000000ABCD
r64a_1 = 0x0000ABCD00000000
r64b_0 = 0x00000000ABCD0000
r64b_1 = 0xABCD000000000000
r64c_0 = 0x000000000000ABCD
r64c_1 = 0x00000000ABCD0000
r64c_2 = 0x0000ABCD00000000
r64c_3 = 0xABCD000000000000   

This is what I intend from any larger size constructed from smaller size with index value provided if no index then it is always set to the lowest byte from the right.



EDIT



This is my first attempt at trying to do what I intend to do, and here I'm using a lambda template. I created this lambda and it is in my class's header file above after the using's and before the class declaration within my namespace.

template<typename P, typename T>
auto wordSize = [](T& t, P& p, const u8 idx) {
    p &= ~(0xFF << (idx * 8));
    P tmp = static_cast<P>( t );
    tmp = (tmp << (idx * 8));
    p |= tmp;
    return p;
};

Now in try to use this in my second case constructor:

template<typename P, std::enable_if_t<(sizeof(P) < sizeof(T))>* = nullptr>
explicit Register(P val, const u8 idx = 0) :
    data{ static_cast<T>( wordSize<T,P>(val, data, idx ) ) },
    value{ data },
    bits{ data }
{
    constexpr u16 sizeT = sizeof(T);
    constexpr u16 sizeP = sizeof(P);
    assert((idx >= 0) && (idx <= ((sizeT / sizeP) - 1)) );
}

Except here I had to change the Constructor's parameter for <P> from const <P> to just <P> for this to work. Now when I construct my Register types from smaller types I am inserting the correct word or byte into the right index location, however the rest of the bits are not being 0 initialized.

Example:

Reg8    r8{ 0xAA };
Reg32 r32a{ r8, 0 };
Reg32 r32b{ r8, 1 };
Reg32 r32c{ r8, 2 };
Reg32 r32d{ r8, 3 };

// Expected Binary Output in Hex:
r8   = 0xAA
r32a = 0x000000AA
r32b = 0x0000AA00
r32c = 0x00AA0000
r32d = 0xAA000000

// Actual Outputs:
r8   = 0xAA
r32a = 0xCCCCCCAA
r32b = 0xCCCCAACC
r32c = 0xCCAACCCC
r32d = 0xAACCCCCC

I'm very close to achieving my goals, but now I just need to adjust this so that all of the CC are 00.

c++
templates
c++17
list-initialization
asked on Stack Overflow May 18, 2019 by Francis Cugler • edited May 18, 2019 by Francis Cugler

1 Answer

1

To correctly initialize any integer type T with a shift from any integral P val, use

data{static_cast<T>(static_cast<T>(val) << sizeof(P)*CHAR_BIT*idx)}

The inner cast is necessary for the shift to be well defined for T wider than int; the outer one is necessary to counteract promotion for T narrower than int (or just use parentheses rather than braces to allow the narrowing conversion).

answered on Stack Overflow May 18, 2019 by Davis Herring

User contributions licensed under CC BY-SA 3.0