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

How to define conversion operator to permit built-in operator += to work for adding two classes

我将 类 定义为 Windows 基于 PC 的嵌入式代码仿真环境中硬件寄存器的替代品。为了无缝地工作并在嵌入式固件代码中看起来就像它们是硬件寄存器一样,类 必须隐式转换 to/from 它们适当的标准固定宽度类型(uint8_tuint16_t, uint32_t, 以及他们签名的副本),基于硬件寄存器的大小和类型。

类 工作得很好(在 SO 答案的帮助下)除了编译器无法弄清楚它需要进行隐式转换来处理 +=当两个这样的类型在 += 操作中组合时的操作:

#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 = d + b; 而不是 d += b; 但这些 类 的全部目的是像真正的硬件寄存器一样操作,一个是定义为模拟和测试环境的 uint8_tuint16_tuint32_t 等。我不想为了能够在模拟环境中运行而对嵌入式固件施加奇怪的限制。

有什么我可以更改或添加到我的 类 以使编译器能够进行适当的隐式转换以启用标准内置的内置 operator+=类型?我想避免为这些 类 的组合定义我自己的 operator+= 方法,因为必须处理很多排列;我宁愿编译器的隐式转换为我完成工作。

大约 25 年前,我曾为这个问题苦苦挣扎。今晚我搜索了一个我当时写的模板来帮助处理这些事情。我会把它给你,但我很遗憾它可能丢失了。这是一个 mix-in,有点像下面这个。我认为它至少解决了 proliferation-of-operator-signatures 问题。

感谢 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 更新:

这就是我最终使用的基础 class 每个 REG class:

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

I'd like to avoid having to define my own operator+= methods for combinations of these classes because that's a lot of permutations to have to handle

但编译器可以为您处理所有这些。一阶近似值:

#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

阅读@JiveJadson 的回答,我想知道 - 为什么不只使用一个模板 class?

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