Matteo Monti
Matteo Monti

Reputation: 8960

Wrapping arithmetic types in C++

C++ is awesome, but you cannot inherit from arithmetic types, which, sometimes, would be useful. I wrote the following:

template <typename type> class arithmetic
{
    static_assert(std :: is_arithmetic <type> :: value, "Please provide an arithmetic type.");

    // Members

    type _value;

public:

    // Constructors

    inline arithmetic() = default;

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline arithmetic(const rtype &);

    // Arithmetic operators

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator + (const rtype &) const;

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator - (const rtype &) const;

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator * (const rtype &) const;

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator / (const rtype &) const;

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator % (const rtype &) const;

    inline auto operator + () const;
    inline auto operator - () const;

    inline auto operator ++ ();
    inline auto operator ++ (int);

    inline auto operator -- ();
    inline auto operator -- (int);

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator = (const rtype &);

    // Comparison operators

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator == (const rtype &) const;

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator != (const rtype &) const;

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator > (const rtype &) const;

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator < (const rtype &) const;

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator >= (const rtype &) const;

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator <= (const rtype &) const;

    // Logical operators

    inline auto operator ! () const;

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator && (const rtype &) const;

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator || (const rtype &) const;

    // Bitwise operators

    inline auto operator ~ () const;

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator & (const rtype &) const;

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator | (const rtype &) const;

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator ^ (const rtype &) const;

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator << (const rtype &) const;

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator >> (const rtype &) const;

    // Compound assignment operators

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator += (const rtype &);

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator -= (const rtype &);

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator *= (const rtype &);

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator /= (const rtype &);

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator %= (const rtype &);

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator &= (const rtype &);

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator |= (const rtype &);

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator ^= (const rtype &);

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator <<= (const rtype &);

    template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type * = nullptr> inline auto operator >>= (const rtype &);

    // Member and pointer operators

    inline type * operator & ();
    inline const type * operator & () const;

    // Casting

    inline operator type & ();
    inline operator const type & () const;
};

// Constructors

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline arithmetic <type> :: arithmetic(const rtype & value) : _value(value)
{
}

// Arithmetic operators

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator + (const rtype & rvalue) const
{
    return this->_value + rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator - (const rtype & rvalue) const
{
    return this->_value - rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator * (const rtype & rvalue) const
{
    return this->_value * rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator / (const rtype & rvalue) const
{
    return this->_value / rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator % (const rtype & rvalue) const
{
    return this->_value % rvalue;
}

template <typename type> inline auto arithmetic <type> ::  operator + () const
{
    return +(this->_value);
}

template <typename type> inline auto arithmetic <type> ::  operator - () const
{
    return -(this->_value);
}

template <typename type> inline auto arithmetic <type> ::  operator ++ ()
{
    return ++(this->_value);
}

template <typename type> inline auto arithmetic <type> ::  operator ++ (int)
{
    return (this->_value)++;
}

template <typename type> inline auto arithmetic <type> ::  operator -- ()
{
    return --(this->_value);
}

template <typename type> inline auto arithmetic <type> ::  operator -- (int)
{
    return (this->_value)++;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator = (const rtype & rvalue)
{
    return this->_value = rvalue;
}

// Comparison operators

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator == (const rtype & rvalue) const
{
    return this->_value == rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator != (const rtype & rvalue) const
{
    return this->_value != rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator > (const rtype & rvalue) const
{
    return this->_value > rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator < (const rtype & rvalue) const
{
    return this->_value < rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator >= (const rtype & rvalue) const
{
    return this->_value >= rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator <= (const rtype & rvalue) const
{
    return this->_value <= rvalue;
}

// Logical operators

template <typename type> inline auto arithmetic <type> ::  operator ! () const
{
    return !(this->_value);
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator && (const rtype & rvalue) const
{
    return this->_value && rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator || (const rtype & rvalue) const
{
    return this->_value || rvalue;
}

// Bitwise operators

template <typename type> inline auto arithmetic <type> ::  operator ~ () const
{
    return ~(this->_value);
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator & (const rtype & rvalue) const
{
    return this->_value & rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator | (const rtype & rvalue) const
{
    return this->_value | rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator ^ (const rtype & rvalue) const
{
    return this->_value ^ rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator << (const rtype & rvalue) const
{
    return this->_value << rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator >> (const rtype & rvalue) const
{
    return this->_value >> rvalue;
}

// Compound assignment operators

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator += (const rtype & rvalue)
{
    return this->_value += rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator -= (const rtype & rvalue)
{
    return this->_value -= rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator *= (const rtype & rvalue)
{
    return this->_value *= rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator /= (const rtype & rvalue)
{
    return this->_value /= rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator %= (const rtype & rvalue)
{
    return this->_value %= rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator &= (const rtype & rvalue)
{
    return this->_value &= rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator |= (const rtype & rvalue)
{
    return this->_value |= rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator ^= (const rtype & rvalue)
{
    return this->_value ^= rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator <<= (const rtype & rvalue)
{
    return this->_value <<= rvalue;
}

template <typename type> template <typename rtype, typename std :: enable_if <__arithmetic :: __is_arithmetic_convertible <rtype> :: value> :: type *> inline auto arithmetic <type> ::  operator >>= (const rtype & rvalue)
{
    return this->_value >>= rvalue;
}

// Member and pointer operators

template <typename type> inline type * arithmetic <type> :: operator & ()
{
    return &(this->_value);
}

template <typename type> inline const type * arithmetic <type> :: operator & () const
{
    return &(this->_value);
}

// Casting

template <typename type> inline arithmetic <type> :: operator type & ()
{
    return this->_value;
}

template <typename type> inline arithmetic <type> :: operator const type & () const
{
    return this->_value;
}

Which is basically just a very pedantic wrapper for arithmetic types. The wrapper has an arithmetic member called _value, then all the calls to any operator are forwarded down to _value, and there is a cast operator to the original arithmetic type.

Now, I wonder, is there any circumstance in which, say, an arithmetic <int> will behave differently than an int? I can't seem to figure out any, but I thought I'd ask a more informed opinion.

On the other hand, if this works as expected and arithmetic <int> behaves as an int, then why isn't this part of the standard? It looks quite trivial to implement and would allow us to extend arithmetic types as we please.

Upvotes: 2

Views: 762

Answers (2)

user48956
user48956

Reputation: 15788

Anywhere an int type is explicitly expected but arithmetic<int> wrapper is provided, you should be concerned. Defining implicit conversions will help this in some cases, but won't help where any of the following are involved:

  • int references. A f(int&) cannot receive an arithmetic<int>&. Also, here, converting your object to an in will lead to a temporary variable being modified, not your original object. Function of the f(int const&) can reasonably by expected to behave well, since you should expect the function to not hold onto the reference (but its not guaranteed). Generally speaking, you'll also get more mileage out of implicit conversions if your ints and objects are treated as immutable variables.

  • int pointers. For the same reason. An int* is not a arithmetic<int>*

  • int arrays. An arithmetic<int>[] cannot be supplied where an int[] is explicitly required.
  • any template type of int. A vector<arithmetic<int>> cannot be used where a vector<int> is required.
  • A F<arithmetic<int>> cannot be provided where F<T> has specialization for F<int> but not for the generic case and you're prevented from providing your own specialization (hmm... is this possible? For example, by defining all known specialization privately in a .cxx).

Also, instantiating F where F declares. If your class is truly an arithmetic type, you must also be able to declare arithmetic<int>. In your example:

static_assert(std :: is_arithmetic <type> :: value, "Please provide an arithmetic type.");

will cause it to fail. (Unless you specialize is_arithmetic> for all T.)

Upvotes: 0

Igor Tandetnik
Igor Tandetnik

Reputation: 52591

One issue off the top - no more than one user-defined conversion in an implicit conversion sequence. Consider:

class C { C(int); };
void f(C);

f(42);  // works, calls f(C(42));
f(arithmetic<int>(42));  // wouldn't work.

Another issue - template specializations:

template <typename T> void f(T) { std::cout << "Generic"; }
template <> void f<int>(int) { std::cout << "Specialized"; }

f(42);  // calls specialized
f(arithmetic<int>(42));  // calls generic

Closer to home - you can use arithmetic<int> but not arithmetic<arithmetic<int>>. More generally, various template metaprogramming techniques would be able to tell the difference.

Upvotes: 3

Related Questions