如何定义转换运算符以允许内置运算符+ =用于添加两个类

时间:2018-03-04 04:06:05

标签: c++ c++11 visual-studio-2015

我将类定义为基于Windows PC的嵌入式代码仿真环境中硬件寄存器的替身。为了无缝地工作并且看起来像嵌入式固件代码就像它们是硬件寄存器一样,这些类必须隐式地转换为适当的标准固定宽度类型(uint8_tuint16_t,{{1}根据尺寸和尺寸,以及他们签署的对应物。硬件寄存器的类型。

这些类工作得很漂亮(在this SO答案的帮助下)除了编译器无法确定它需要进行隐式转换以处理uint32_t操作时,两个这样的类型是在+=操作中合并:

+=

如上所示,代替#include <iostream> class REG16 { private: uint16_t _i; public: REG16() = default; constexpr REG16(const unsigned int i) : _i((uint16_t)i * 2) {} constexpr operator uint16_t() const { return _i / 2; } REG16& operator=(const unsigned int i) { _i = (uint16_t)i * 2; } }; class REG32 { private: uint32_t _i; public: REG32() = default; constexpr REG32(const uint32_t i) : _i(i * 2) {} constexpr operator uint32_t() const { return _i / 2; } REG32& operator=(const uint32_t i) { _i = i * 2; return *this; } }; int main() { REG16 a(12); REG32 b(12); uint32_t c; REG32 d; // Works great: 'a' is implicitly converted to a uint16_t, then added to 'c': c = 0; c += a; std::cout << "c = 0; c += a; result: c = " << c << std::endl; // Works great: 'b' is implicitly converted to a uint32_t, then added to 'c': c = 0; c += b; std::cout << "c = 0; c += b; result: c = " << c << std::endl; // Works great: 'b' and 'd' are both implicitly converted to uint32_t's, // then added together as uint32_t's, and assigned back to 'd' with the // REG32::operator=() asignment operator: d = 0; d = d + b; std::cout << "d = 0; d += b; result: d = " << d << std::endl; // Does not work: // error C2676: binary '+=': 'REG32' does not define this operator or a conversion to a type acceptable to the predefined operator d = 0; d += b; // <------ line with the error std::cout << "d = 0; d += b; result: d = " << d << std::endl; } 我可以做d += b;,但这些类的整个目的是像真正的硬件寄存器那样操作,一个被定义为{{1} }},d = d + b;uint8_t等用于模拟&amp;测试环境。我不想对嵌入式固件施加奇怪的限制,只是为了能够在模拟环境中运行。

我是否可以更改或添加到我的类中以使编译器能够执行适当的隐式转换以使用内置的uint16_t内置的标准类型?我想避免为这些类的组合定义我自己的uint32_t方法,因为这是必须处理的许多排列;我宁愿编译器的隐式转换为我做的工作。

3 个答案:

答案 0 :(得分:3)

25年前,我一直在努力解决这个问题。今晚我搜索了一个我写回来的模板,以帮助解决这些问题。我会把它给你,但我很遗憾它可能会丢失。这是一个混合,有点像下面的那个。我认为它至少解决了操作员签名扩散问题。

感谢向Ben Voigt提出建议CRTP。

感谢让Jens改进代码。

感谢Mike Kinghan进行回归测试。

#include <cstdint>
using std::uint32_t;
using std::uint16_t;

template<typename Derived, typename Value_t>
class number {
public:

    constexpr number(Value_t& x) : i(x) {}

    template<class R>
    Derived&
        operator+= (const R& r) {
        i += static_cast<Value_t>(r); return static_cast<Derived&>(*this);
    }
    // ... and scads of other operators that I slavishly typed out.
protected:
    ~number() {} // to prevent slicing
private:
    Value_t & i;
};


class REG16: public number<REG16, uint16_t>
{
private:
    uint16_t _i;
    using Num = number<REG16, uint16_t>;
public:
    REG16() : Num(_i) {}
    constexpr REG16(const unsigned int i) : _i(i), Num(_i) {}
    constexpr operator uint16_t() const { return _i; }
    REG16& operator=(const unsigned int i) { _i = i; }
};

class REG32 : public number<REG32, uint32_t>
{
private:
    uint32_t _i;
    using Num = number<REG32, uint32_t>;
public:
    REG32() : Num(_i) {}
    constexpr REG32(const uint32_t i) : _i(i), Num(_i) {}
    constexpr operator uint32_t() const { return _i; }
    REG32& operator=(const uint32_t i) { _i = i; return *this; }
};
#include <iostream>
int main()
{
    REG16 a(12);
    REG32 b(12);
    uint32_t c;
    REG32 d;

    // Works great: 'a' is implicitly converted to a uint16_t, then added to 'c':
    c = 0;
    c += a;
    std::cout << "c = 0; c += a;     result:   c = " << c << std::endl;

    // Works great: 'b' is implicitly converted to a uint32_t, then added to 'c':
    c = 0;
    c += b;
    std::cout << "c = 0; c += b;     result:   c = " << c << std::endl;

    // Works great: 'b' and 'd' are both implicitly converted to uint32_t's,
    // then added together as uint32_t's, and assigned back to 'd' with the
    // REG32::operator=() asignment operator:
    d = 0;
    d = d + b;
    std::cout << "d = 0; d += b;     result:   d = " << d << std::endl;

    // DID NOT WORK
    // error C2676: binary '+=': 'REG32' does not define this operator or a conversion to a type acceptable to the predefined operator
    d = 0;
    d += b; // <------ line that had the error
    std::cout << "d = 0; d += b;     result:   d = " << d << std::endl;
}

phonetagger更新:

这实际上我最终用作每个REG类的基类:

// numericCompoundAssignmentBase is a template base class for use with types that implement numeric behavior
template<typename DT, typename NT> // DerivedType, NativeType (native types: uint8_t, uint16_t, etc.)
struct numericCompoundAssignmentBase
{
   NT  operator++ (int) { DT& dt = static_cast<DT&>(*this); NT x(dt); dt = x + 1;  return x; } // postfix ++
   NT  operator-- (int) { DT& dt = static_cast<DT&>(*this); NT x(dt); dt = x - 1;  return x; } // postfix --
   DT& operator++ ()    { DT& dt = static_cast<DT&>(*this); NT x(dt); dt = x + 1;  return dt; } // prefix --
   DT& operator-- ()    { DT& dt = static_cast<DT&>(*this); NT x(dt); dt = x - 1;  return dt; } // prefix --
   DT& operator+= (const NT& r) { DT& dt = static_cast<DT&>(*this);  dt = dt + r;  return dt; }
   DT& operator-= (const NT& r) { DT& dt = static_cast<DT&>(*this);  dt = dt - r;  return dt; }
   DT& operator*= (const NT& r) { DT& dt = static_cast<DT&>(*this);  dt = dt * r;  return dt; }
   DT& operator/= (const NT& r) { DT& dt = static_cast<DT&>(*this);  dt = dt / r;  return dt; }
   DT& operator%= (const NT& r) { DT& dt = static_cast<DT&>(*this);  dt = dt % r;  return dt; }
   DT& operator&= (const NT& r) { DT& dt = static_cast<DT&>(*this);  dt = dt & r;  return dt; }
   DT& operator|= (const NT& r) { DT& dt = static_cast<DT&>(*this);  dt = dt | r;  return dt; }
   DT& operator^= (const NT& r) { DT& dt = static_cast<DT&>(*this);  dt = dt ^ r;  return dt; }
   DT& operator<<=(const NT& r) { DT& dt = static_cast<DT&>(*this);  dt = dt << r; return dt; }
   DT& operator>>=(const NT& r) { DT& dt = static_cast<DT&>(*this);  dt = dt >> r; return dt; }
   private: numericCompoundAssignmentBase() = default; friend DT; // ensures the correct 'DT' was specified
};

答案 1 :(得分:2)

阅读@ JiveJadson的答案,我在想 - 为什么不只是一个模板化的课程呢?

template<typename Integer>
class register_t {
public:
    using value_type = Integer;
    register_t(Integer& val) : value_(val << 1) {}
    register_t(const register_t&) = default;
    register_t(register_t&&) = default;
    register_t& operator=(const register_t&) = default;
    register_t& operator=(register_t&&) = default;
    operator Integer() { return value_ >> 1; }

    template <typename RHSInteger>
    register_t& operator+= (const RHSInteger& rhs) {
        value_ += static_cast<Integer>(rhs) << 1; 
        return value_;
    }
private:
    Integer value_;
      // bottom bit is 0, rest of bits hold the actual value
};

答案 2 :(得分:1)

  

我想避免为组合定义我自己的operator + =方法   这些类因为这需要处理很多排列

但是编译器可以为你处理它们。第一个近似值:

#include <iostream>
#include <cstdint>

class REG16
{
private:
   uint16_t _i;
public:
   REG16() = default;
   constexpr REG16(const unsigned int i) : _i((uint16_t)i * 2) {}
   constexpr operator uint16_t() const { return _i / 2; }
   REG16& operator=(const unsigned int i) { _i = (uint16_t)i * 2; return *this; }
   template<typename T>
   REG16& operator+=(T && lhs) {
       return *this = *this + std::forward<T>(lhs); 
   }
};

class REG32
{
private:
   uint32_t _i;
public:
   REG32() = default;
   constexpr REG32(const uint32_t i) : _i(i * 2) {}
   constexpr operator uint32_t() const { return _i / 2; }
   REG32& operator=(const uint32_t i) { _i = i * 2; return *this; }
   template<typename T>
   REG32& operator+=(T && lhs) {
       return *this = *this + std::forward<T>(lhs);
   }
};

Live VC++

Live GCC

如果您或其他任何人编写了试图实例化的代码:

template<typename T>
REG{16|32}& operator+=(T && lhs) {
    return *this = *this + std::forward<T>(lhs); 
}

,类型T,其中包含:

*this + lhs

未定义,因此根本无法编译。

但是这个解决方案进一步关注REG16REG32这一事实 与成员_i的类型相同。所以还是更好 他们每个人都是一个模板的专业化:

#include <utility>
#include <cstdint>

template<typename IntType>
struct REG_N
{
    REG_N() = default;
    constexpr REG_N(const IntType i) 
    : _i(static_cast<IntType>(i) * 2) {}
    constexpr operator IntType() const {
       return _i / 2;
    }
    REG_N & operator=(const IntType i) { 
        _i = static_cast<IntType>(i) * 2; 
        return *this;
    }
    template<typename T>
    REG_N & operator+=(T && lhs) {
       return *this = *this + std::forward<T>(lhs); 
    }
private:
   IntType _i;

};

using REG16 = REG_N<std::uint16_t>;
using REG32 = REG_N<std::uint32_t>;

Live VC++

Live GCC