Преобразование набора классов в шаблоны классов и избежание неоднозначности конструктора - PullRequest
0 голосов
/ 14 мая 2019

Я постараюсь сделать этот вопрос настолько коротким, насколько смогу, но есть достаточный объем кода, который нужно показать, чтобы понять, чего я пытаюсь достичь и как решить мою текущую проблему.

Вот мои оригинальные объявления классов со всеми их конструкторами:

Register.h - Оригинальная версия

#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);

Теперь я пошели превратил их в шаблоны классов, чтобы уменьшить количество дублирования кода.И вот что у меня есть:

Register.h - Более новая версия

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>;

Теперь, когда дело доходит до конструкторов,примет тип std::uintx_t, а также значение индекса.Например, некоторые объявления конструктора соответствуют:

В исходной версии Reg8 имеет Reg8(u32 val, u8 idx) и Reg16 имеет Reg16(u32 val, u8 idx).И если вы посмотрите поближе, Reg8(...) утверждает, что idx <= 0 && idx >= 3, в то время как Reg16(...) утверждает, что idx == 0 || idx == 1.

Однако, когда я пытаюсь шаблонировать эти классы и переносить их через конструкторы, они теперь становятся неоднозначными.Я не знаю, как определить, какое утверждение использовать, чтобы различать его как Reg8, Reg16, Reg32 и т.д ...

Ответы [ 3 ]

1 голос
/ 14 мая 2019

Мне кажется, все в вашем классе сводится к использованию sizeof и numeric_limits::max неподписанной версии вашего типа.

Я написал для вас ниже черновик того, как, я думаю, класс мог бы выглядеть:

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;
}
1 голос
/ 14 мая 2019

Если вы хотите определить тип и отреагировать соответствующим образом, тогда зачем вообще использовать общие шаблоны?Есть два варианта: определить тип и ветвь (вообще не используя шаблоны, специализируя свои шаблоны или, возможно, используя SFINAE) или написать real универсальный код.Первый не очень полезен, потому что в итоге вы получите больше котла, чем шаблонов.

Последний зависит от ваших требований, но может выглядеть примерно так:

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;
};

По запросу, вот пример того, как специализироваться (используя базовый класс для уменьшения избыточности):

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...
  }
};
0 голосов
/ 14 мая 2019

Я думаю, что решил свою проблему;Я могу скомпилировать, собрать и запустить, и я получаю некоторые ожидаемые результаты, но я не выполнил достаточно модульного тестирования, чтобы проверить, все ли случаи работают должным образом, но это то, что я придумал до сих пор.Я разбил свой код на два файла соответственно, чтобы это работало ...

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

Теперь я определил их в Register.cpp вчтобы предотвратить 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

Теперь моя прикладная программа:

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;
}

И я получаю это для вывода:

Reg16(1420)
hex: 0x058C
bin: 0000010110001100

Reg8(140)
hex: 0x8C
bin: 10001100

Reg8(5)
hex: 0x05
bin: 00000101
...