Converting a set of classes to class templates and avoiding constructor ambiguity

0

I will try to make this question as short as I can but there is a good amount of code to show in order for one to understand what I'm trying to achieve and how to resolve my current issue.

Here are my original class declarations with all of their constructors:

Register.h - Original Version

#include <bitset>
#include <cassert>
#include <cstdint>
#include <iostream>

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

const u16 BYTE = 0x08, WORD = 0x10, DWORD = 0x20, QWORD = 0x40;

typedef std::bitset<BYTE> Byte;
typedef std::bitset<WORD> Word;
typedef std::bitset<DWORD> DWord;
typedef std::bitset<QWORD> QWord;

template<typename T>
void getByteFrom(T val, u8 idx, u8& res) {
    res = ((val >> (idx * 8) & 0xff));
}

template<typename T>
void getWordFrom(T val, u8 idx, u16& res) {
    res = ((val >> (idx * 16) & 0xffff));
}

template<typename T>
void getDWordFrom(T val, u8 idx, u32& res) {
    res = ((val >> (idx * 32) & 0xffffffff));
}

template<typename T>
struct Register {
    T data;
    Register() = default;
};

struct Reg8 : public Register<u8> {
    u8 value;  // must be declared before std::bitset<T>
    Byte bits;

    // Default 0 Initialized Constructor
    Reg8() : value{ 0 }, bits{ value } { this->data = 0; }

    // Constructors by Register Sized Values
    explicit Reg8(u8 val)  : value{ val }, bits{ value } {
        this->data = value;
    }
    explicit Reg8(u16 val) : value{ static_cast<u8>( val ) }, bits{ value } {
        this->data = value;
    }
    explicit Reg8(u32 val) : value{ static_cast<u8>( val ) }, bits{ value } {
        //this->data = value;
    }
    explicit Reg8(u64 val) : value{ static_cast<u8>( val ) }, bits{ value } {
        //this->data = value;
    }

    Reg8(u16 val, u8 idx ) {
        assert( idx == 0 || idx == 1 );
        getByteFrom(val, idx, this->value);
        bits = value;
        this->data = value;
    }

    Reg8(u32 val, u8 idx) {
        assert(idx <= 0 && idx >= 3);
        getByteFrom(val, idx, this->value);
        bits = value;
        this->data = value;
    }

    Reg8(u64 val, u8 idx) {
        assert(idx <= 0 && idx >= 7);
        getByteFrom(val, idx, this->value);
        bits = value;
        this->data = value;
    }

    // Constructors by Register Types
    template<typename T>
    explicit Reg8(Register<T>* reg) {
        this->value = static_cast<u8>( reg->data );
        this->bits = value;
    }
};

struct Reg16 : public Register<u16> {
    u16  value;  // must be declared before std::bitset<T>
    Word bits;

    // Default 0 Initialized Constructor
    Reg16() : value{ 0 }, bits{ value } { this->data = 0; }

    // Constructors by Register Sized Values
    explicit Reg16(u16& val) : value{ val }, bits{ value } {
        this->data = value;
    }
    explicit Reg16( u8& val) : value{ val }, bits{ value } {
        this->data = value;
    }
    explicit Reg16(u32& val) : value{ static_cast<u16>(val) }, bits{ value } {
        this->data = value;
    }
    explicit Reg16(u64& val) : value{ static_cast<u16>(val) }, bits{ value } {
        this->data = value;
    }

    Reg16( u32 val, u8  idx) {
        assert(idx == 0 || idx == 1);
        getWordFrom(val, idx, this->value);
        bits = value;
        this->data = value;
    }

    Reg16(u64 val, u8 idx) {
        assert(idx <= 0 || idx <= 3);
        getWordFrom(val, idx, this->value);
        bits = value;
        this->data = value;
    }

    // Constructors by Register Types
    template<typename T>
    explicit Reg16(Register<T>* reg) {
        this->value = static_cast<u16>(reg->data);
        this->bits = value;
    }

};

struct Reg32 : public Register<u32> {
    u32 value;  // must be declared before std::bitset<T>
    DWord bits;

    // Default 0 Initialized Constructor
    Reg32() : value{ 0 }, bits{ value } { this->data = 0; }

    // Constructors by Register Sized Values
    explicit Reg32(u32& val) : value{ val }, bits{ value } {
        this->data = value;
    }
    explicit Reg32( u8& val) : value{ val }, bits{ value } {
        this->data = value;
    }
    explicit Reg32(u16& val) : value{ val }, bits{ value } {
        this->data = value;
    }
    explicit Reg32(u64& val) : value{ static_cast<u32>(val) }, bits{ value } {
        this->data = value;
    }   

    Reg32(u64 val, u8 idx) {
        assert(idx == 0 || idx == 1);
        getDWordFrom(val, idx, this->value);
        bits = value;
        this->data = value;
    }

    // Constructors by Register Types
    template<typename T>
    explicit Reg32(Register<T>* reg) {
        this->value = static_cast<u32>(reg->data);
        this->bits = value;
    }
};

struct Reg64 : public Register<u64> {
    u64 value;  // must be declared before std::bitset<T>
    QWord bits;

    // Default 0 Initialized Constructor
    Reg64() : value{ 0 }, bits{ value } { this->data = 0; }

    // Constructors by Register Sized Values
    explicit Reg64(u64& val) : value{ val }, bits{ value }{
        this->data = value;
    }
    explicit Reg64( u8& val) : value{ val }, bits{ value } {
        this->data = value;
    }
    explicit Reg64(u16& val) : value{ val }, bits{ value } {
        this->data = value;
    }
    explicit Reg64(u32& val) : value{ val }, bits{ value } {
        this->data = value;
    }


    // Constructors by Register Types
    template<typename T>
    explicit Reg64(Register<T>* reg) {
        this->value = static_cast<u64>(reg->data);
        this->bits = value;
    }
};

std::ostream& operator<<(std::ostream& os, const Reg8& r);
std::ostream& operator<<(std::ostream& os, const Reg16& r);
std::ostream& operator<<(std::ostream& os, const Reg32& r);
std::ostream& operator<<(std::ostream& os, const Reg64& r);

Now I went and turned these into template classes to reduce a lot of the code duplication. And this is what I have so far:

Register.h - Newer Version

template<typename Ty>
struct Register_t {
    static constexpr u16 BitCount = sizeof(Ty) * CHAR_BIT;

    Ty currentValue;
    Ty previousValue;
    std::bitset<BitCount> bits;

    Register_t() : 
        currentValue{ 0 }, 
        previousValue{ 0 }, 
        bits{ 0 }{}

    template<typename U>
    explicit Register_t(U val) : 
        currentValue{ static_cast<Ty>(val) }, 
        previousValue{ 0 }, 
        bits{ currentValue } {}

    template<typename U>
    explicit Register_t(Register_t<U>& r) {
        this->currentValue = static_cast<Ty>(r->currentValue);
        this->bits = r->bits;
    }        
};

template<typename Ty>
struct Register : public Register_t<Ty> {
    Register() = default;
    explicit Register(Ty val) : Register_t<Ty>( val ) {}    

    // Reg8
    template<typename U>
    Register( u16 val, u8 idx) {
        assert(idx == 0 || idx == 1);
        getByteFrom(val, idx, currentValue);
        this->bits = this->currentValue;
    }

    Register(u32 val, u8 idx) {
        assert(idx <= 0 && idx >= 3);
        getByteFrom(val, idx, this->currentValue);
        this->bits = this->currentValue;
    }

    Register(u64 val, u8 idx) {
        assert(idx <= 0 && idx <= 7);
        getByteFrom(val, idx, this->currentValue);
        this->bits = this->currentValue;
    }

    // Reg16
    Register(u32 val, u8 idx) {
        assert(idx == 0 || idx == 1);
        getWordFrom(val, idx, this->currentValue);
        this->bits = this->currentValue;
    }

    Register(u64 val, u8 idx) {
        assert(idx <= 0 && idx <= 3);
        getWordFrom(val, idx, this->currentValue);
        this->bits = this->currentValue;
    }

    // Reg32
    Register(u64 val, u8 idx) {
        assert(idx == 0 || idx == 1);
        getDWordFrom(val, idx, this->currentValue);
        this->bits = this->currentValue;
    }
};

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

Now when it comes to the constructors that will take a std::uintx_t type as well as an index value. Some of the constructor declarations match for example:

In the original version Reg8 has Reg8(u32 val, u8 idx) and Reg16 has Reg16(u32 val, u8 idx). And if you look closer Reg8(...) asserts that idx <= 0 && idx >= 3 while Reg16(...) asserts that idx == 0 || idx == 1.

However when I try to template these classes and port over the constructors, these now become ambiguous. I don't know how to determine which assert to use to distinguish between it being an Reg8, Reg16, Reg32 etc...

c++
templates
constructor
c++17
disambiguation
asked on Stack Overflow May 14, 2019 by Francis Cugler

3 Answers

1

If you want to determine the type and react appropriately, then why using generic templates at all? There are two options, determine the type and branch (by using no templates at all, by specializing your templates or possibly by using SFINAE) or write real generic code. The former isn't very useful, because you will end up with more boiler-plate than with no templates.

The latter depends on your requirements, but it could look something like this:

template <typename T>
T getXFrom(T val, std::uint8_t idx) {
  return val >> (idx * CHAR_BIT);
}

template <typename T>
class Register {
 public:
  template <typename U>
  Register(U val, std::uint8_t idx) {
    static_assert(std::is_integral_v<U>);
    assert(idx >= 0 && idx < sizeof(U));
    currentValue = getXFrom(val, idx);
  }

 private:
  T currentValue;
};

As requested, here is an example on how to specialize (using a base class to reduce redundancies):

template <typename T>
class Register {
 protected:
  T currentValue;
};

template <typename>
class RegisterImpl;

template <>
class RegisterImpl<uint8_t> : Register<uint8_t> {
 public:
  template <typename U>
  RegisterImpl(U val, std::uint8_t idx) {
    // uint8_t asserts...
  }
};

template <>
class RegisterImpl<uint16_t> : Register<uint16_t> {
 public:
  template <typename U>
  RegisterImpl(U val, std::uint8_t idx) {
    // uint16_t asserts...
  }
};
answered on Stack Overflow May 14, 2019 by fdan • edited May 14, 2019 by fdan
1

To me it looks like everything in your class comes down to using sizeof and numeric_limits::max of the unsigned version of your type.

I've written up for you below a rough draft of how I think the class could look:

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

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

    template <typename P>
    explicit Register(const P val) : data(static_cast<T>(val)), value(data), bits(data) {}

    template <typename P>
    Register(const P val, const unsigned char idx) : data(static_cast<T>((val >> std::size(bits) * idx) & numeric_limits<make_unsigned_t<T>>::max())), value(data), bits(data) {
        assert(idx == '\0' || idx < sizeof(P) / sizeof(T));
    }

    template <typename P>
    Register(const Register<P>& reg) : data(static_cast<T>(reg.data)), value(data), bits(data) {}
};

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

  for(std::size_t i = 0; i < size(r.bits); ++i) {
    cout.put('0' + r.bits[i]);
  }
  return os << endl << endl;
}

template <>
ostream& operator<<<unsigned char>(ostream& os, const Register<unsigned char>& r) {
  os << "Reg" << size(r.bits) << '(' << static_cast<int>(r.data) << ")\nhex: 0x" << uppercase << setfill('0') << setw(sizeof(unsigned char) * 2) << hex << static_cast<int>(r.data) << dec << "\nbin: ";

  for(std::size_t i = 0; i < size(r.bits); ++i) {
    cout.put('0' + r.bits[i]);
  }
  return os << endl << endl;
}
answered on Stack Overflow May 14, 2019 by Jonathan Mee • edited May 15, 2019 by Jonathan Mee
0

I think I have solved my problem; I am able to compile, build and run and I am getting some expected results, but I have not done enough unit testing to verify if all cases work as expected, but this is what I have came up with so far. I had split my code into two files respectively for this to work...

Register.h

#pragma once

#include <algorithm>
#include <assert.h>
#include <bitset>
#include <cstdint>

namespace vpc {
    typedef std::int8_t  i8;
    typedef std::int16_t i16;
    typedef std::int32_t i32;
    typedef std::int64_t i64;

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

    const u16 BYTE = 0x08;
    const u16 WORD = 0x10;
    const u16 DWORD = 0x20;
    const u16 QWORD = 0x40;

    typedef std::bitset<BYTE>  Byte;
    typedef std::bitset<WORD>  Word;
    typedef std::bitset<DWORD> DWord;
    typedef std::bitset<QWORD> QWord;

    // Helper Functions
    template<typename T>
    void getByteFrom(T val, u8 idx, u8& res) {
        res = ((val >> (idx * 8) & 0xff));
    }

    template<typename T>
    void getWordFrom(T val, u8 idx, u16& res) {
        res = ((val >> (idx * 16) & 0xffff));
    }

    template<typename T>
    void getDWordFrom(T val, u8 idx, u32& res) {
        res = ((val >> (idx * 32) & 0xffffffff));
    }

    template<typename T>
    struct Register_t {
        static constexpr u16 BitCount = sizeof(T) * CHAR_BIT;

        T currentValue;
        T previousValue;
        std::bitset<BitCount> bits;

        Register_t() :
            currentValue{ 0 }, 
            previousValue{ 0 }, 
            bits{ 0 }
        {}

        template<typename U>
        explicit Register_t(U val) : 
            currentValue{ static_cast<T>(val) }, 
            previousValue{ 0 }, 
            bits{ currentValue }
        {}

        template<typename U>
        explicit Register_t(Register_t<U>& r) : previousValue{ 0 }
        {
            this->currentValue = static_cast<T>(r.currentValue);
            this->bits = currentValue;
        }        
    };

    template<typename T>
    struct Register : public Register_t<T> {
        Register() : Register_t<T>() {}
        explicit Register(T val) : Register_t<T>( val ) {}  

        template<typename U>
        explicit Register(Register_t<U>& r) : Register_t<T>( r ) {}

       // These are the constructors with matching declarations
       // that were giving me trouble with ambiguous calls
       Register(u16 val, u8 idx);
       Register(u32 val, u8 idx);
       Register(u64 val, u8 idx);
    };

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

    std::ostream& operator<<(std::ostream& os, const Reg8&  reg);
    std::ostream& operator<<(std::ostream& os, const Reg16& reg);
    std::ostream& operator<<(std::ostream& os, const Reg32& reg);
    std::ostream& operator<<(std::ostream& os, const Reg64& reg);

} // namespace vpc

I now have them defined in Register.cpp in order to prevent LNK Error 2005 - object already defined.

Register.cpp

#include "Register.h"

#include <iostream>
#include <iomanip>

namespace vpc {
    template<>
    Register<u8>::Register(u16 val, u8 idx) {
        assert(idx == 0 || idx == 1);
        getByteFrom(val, idx, currentValue);
        this->bits = this->currentValue;
    }

    template<>
    Register<u8>::Register(u32 val, u8 idx) {
        assert(idx >= 0 && idx <= 3);
        getByteFrom(val, idx, this->currentValue);
        this->bits = this->currentValue;
    }

    template<>
    Register<u8>::Register(u64 val, u8 idx) {
        assert(idx >= 0 && idx <= 7);
        getByteFrom(val, idx, this->currentValue);
        this->bits = this->currentValue;
    }

    // Reg16
    template<>
    Register<u16>::Register(u32 val, u8 idx) {
        assert(idx == 0 || idx == 1);
        getWordFrom(val, idx, this->currentValue);
        this->bits = this->currentValue;
    }

    template<>
    Register<u16>::Register(u64 val, u8 idx) {
        assert(idx >= 0 && idx <= 3);
        getWordFrom(val, idx, this->currentValue);
        this->bits = this->currentValue;
    }

    // Reg32
    template<>
    Register<u32>::Register(u64 val, u8 idx) {
        assert(idx == 0 || idx == 1);
        getDWordFrom(val, idx, this->currentValue);
        this->bits = this->currentValue;
    }

    std::ostream& operator<<(std::ostream& os, const Reg8& r) {
        os << "Reg8(" << +r.currentValue << ")\n"
            << "hex: " << "0x" << std::uppercase
            << std::setfill('0') << std::setw(2) << std::hex
            << +r.currentValue << std::dec << '\n'
            << "bin: " << r.bits << '\n' << std::endl;
        return  os;
    }
    std::ostream& operator<<(std::ostream& os, const Reg16& r) {
        os << "Reg16(" << r.currentValue << ")\n"
            << "hex: " << "0x" << std::uppercase
            << std::setfill('0') << std::setw(4) << std::hex
            << r.currentValue << std::dec << '\n'
            << "bin: " << r.bits << '\n' << std::endl;
        return  os;
    }
    std::ostream& operator<<(std::ostream& os, const Reg32& r) {
        os << "Reg32(" << r.currentValue << ")\n"
            << "hex: " << "0x" << std::uppercase
            << std::setfill('0') << std::setw(8) << std::hex
            << r.currentValue << std::dec << '\n'
            << "bin: " << r.bits << '\n' << std::endl;
        return  os;
    }
    std::ostream& operator<<(std::ostream& os, const Reg64& r) {
        os << "Reg64(" << r.currentValue << ")\n"
            << "hex: " << "0x" << std::uppercase
            << std::setfill('0') << std::setw(16) << std::hex
            << r.currentValue << std::dec << '\n'
            << "bin: " << r.bits << '\n' << std::endl;
        return  os;
    }

} // namespace vpc

Now my application program:

main.cpp

#include <iostream>
#include "Register.h"

int main() {
    using namespace vpc;

    u16 val = 1420;

    Reg16 r16(val);     // used to show a 16 bit register
    Reg8  r8A(val, 0);  // construct an 8 bit register from low byte of val
    Reg8  r8B(val, 1);  // construct an 8 bit register from high byte of val    

    std::cout << r16 << r8A << r8B;

    return EXIT_SUCCESS;
}

And I am getting this for output:

Reg16(1420)
hex: 0x058C
bin: 0000010110001100

Reg8(140)
hex: 0x8C
bin: 10001100

Reg8(5)
hex: 0x05
bin: 00000101
answered on Stack Overflow May 14, 2019 by Francis Cugler • edited May 14, 2019 by Francis Cugler

User contributions licensed under CC BY-SA 3.0