7
\$\begingroup\$

I have a type that models the Arithmetic concept and use it as an opaque typedef (AKA strong typedef, see Note below) in some projects.

I've put the code into its own repository along with some tests and would like some eyes taking a look into it.

Note: for opaque typedef I understand the definition given in N3515.

  • Prior art exist in Boost.Serialization BOOST_STRONG_TYPEDEF, where it seems that the opaque typedefs created model the Arithmetic concept (it is thought to be used for integers) but:
    • This is not clearly stated
    • It doesn't support C++11 features like constexpr, and move semantics
    • It does not inhibit implicit conversions
  • On implicit conversions: I wanted to allow conversions, but prefer them to be explicit. This could be made configurable but I don't think it is worth the trouble.

Some examples of usage:

  • Example 1: disabling implicit conversions

    int a{2};
    long b{3};
    b = a; // works: the implicit conversion is safe
    
    Arithmetic<int> a{2};
    Arithmetic<long> b{3};
    b = a; // error: implicit assignment requires implicit conversion
    b = Arithmetic<long>{a};               // works: explicit construction
    b = static_cast<Arithmetic<long>>(a);  // works: explicit conversion
    
  • Example 2: opaque type-defs

    struct Tag1 {};
    struct Tag2 {};
    
    using Type1 = Arithmetic<int, Tag1>;
    using Type2 = Arithmetic<int, Tag2>;
    Type1 a{2};
    Type2 b{3};
    b = Type2{a};               // works: explicit construction
    b = static_cast<Type2>(a);  // works: explicit conversion
    Type2 c{a};                 // works: explicit construction
    

arithmetic_type.hpp:

#ifndef BOOST_UTILITIES_ARITHMETIC_TYPE_ARITHMETIC_TYPE_
#define BOOST_UTILITIES_ARITHMETIC_TYPE_ARITHMETIC_TYPE_
////////////////////////////////////////////////////////////////////////////////
#include <limits>
#include <type_traits>
////////////////////////////////////////////////////////////////////////////////
namespace boost {
////////////////////////////////////////////////////////////////////////////////

/// \name Index types
///@{
/// \brief Implements an integer type
template <class T, class B = void> struct Arithmetic {
  using value_type = T;
  using type = T;

  /// \name Asignment operators
  ///@{
  constexpr Arithmetic() noexcept(T{T{}}) : value{T{}} {}
  constexpr Arithmetic(const Arithmetic& other) noexcept(T{T{}})
    : value{other.value} {}
  constexpr Arithmetic(Arithmetic&& other) noexcept(T{T{}})
    : value{other.value} {}
  constexpr explicit Arithmetic(const T& other) noexcept(T{T{}})
    : value{other} {}
  template <class U, class V>
  constexpr explicit Arithmetic(const Arithmetic<U, V>& other) noexcept(T{T{}})
    : value(other.value) {}

  constexpr inline Arithmetic& operator=(const Arithmetic& other) noexcept {
    value = other.value;
    return *this;
  }
  constexpr inline Arithmetic& operator=(Arithmetic&& other) noexcept {
    value = other.value;
    return *this;
  }
  constexpr inline Arithmetic& operator=(const T& other) noexcept {
    value = other;
    return *this;
  }
  ///@}

  /// \name Conversion operators
  ///@{
  explicit constexpr inline operator T() noexcept { return value; }
  explicit constexpr inline operator const T() const noexcept { return value; }

  template <class U, class V>
  explicit constexpr inline operator Arithmetic<U, V>() noexcept {
    return value;
  }

  template <class U, class V>
  explicit constexpr inline operator const Arithmetic<U, V>() const noexcept {
    return value;
  }
  ///@}

  /// \name Compound assignment +=, -=, *=, /=
  ///@{
  constexpr inline Arithmetic& operator+=(const Arithmetic& other) noexcept {
    value += other.value;
    return *this;
  }
  constexpr inline Arithmetic& operator-=(const Arithmetic& other) noexcept {
    value -= other.value;
    return *this;
  }
  constexpr inline Arithmetic& operator*=(const Arithmetic& other) noexcept {
    value *= other.value;
    return *this;
  }
  constexpr inline Arithmetic& operator/=(const Arithmetic& other) noexcept {
    value /= other.value;
    return *this;
  }
  ///@}

  /// \name Arithmetic operators +,-,*,/,unary -
  ///@{
  constexpr friend inline Arithmetic operator+(Arithmetic a,
                                               const Arithmetic& b) noexcept {
    return a += b;
  }
  constexpr friend inline Arithmetic operator-(Arithmetic a,
                                               const Arithmetic& b) noexcept {
    return a -= b;
  }
  constexpr friend inline Arithmetic operator*(Arithmetic a,
                                               const Arithmetic& b) noexcept {
    return a *= b;
  }
  constexpr friend inline Arithmetic operator/(Arithmetic a,
                                               const Arithmetic& b) noexcept {
    return a /= b;
  }

  constexpr inline Arithmetic operator-() noexcept {
    static_assert(std::is_signed<T>::value, "Can't negate an unsigned type!");
    return Arithmetic{-value};
  }
  ///@}

  /// \name Prefix increment operators ++(),--()
  ///@{
  constexpr inline Arithmetic& operator++() noexcept {
    ++value;
    return *this;
  }
  constexpr inline Arithmetic& operator--() noexcept {
    --value;
    return *this;
  }
  ///@}

  /// \name Postfix increment operators ()++,()--
  ///@{
  constexpr inline Arithmetic operator++(int) noexcept {
    Arithmetic tmp(*this);
    ++(*this);
    return tmp;
  }
  constexpr inline Arithmetic operator--(int) noexcept {
    Arithmetic tmp(*this);
    --(*this);
    return tmp;
  }
  ///@}

  /// \name Comparison operators ==, !=, <, >, <=, >=
  ///@{
  constexpr friend inline bool operator==(const Arithmetic& a,
                                          const Arithmetic& b) noexcept {
    return a.value == b.value;
  }
  constexpr friend inline bool operator<=(const Arithmetic& a,
                                          const Arithmetic& b) noexcept {
    return a.value <= b.value;
  }
  constexpr friend inline bool operator<(const Arithmetic& a,
                                         const Arithmetic& b) noexcept {
    return a.value < b.value;
  }  // return a <= b && !(a == b) -> slower?
  constexpr friend inline bool operator!=(const Arithmetic& a,
                                          const Arithmetic& b) noexcept {
    return !(a == b);
  }
  constexpr friend inline bool operator>(const Arithmetic& a,
                                         const Arithmetic& b) noexcept {
    return !(a <= b);
  }
  constexpr friend inline bool operator>=(const Arithmetic& a,
                                          const Arithmetic& b) noexcept {
    return !(a < b);
  }
  ///@}

  /// \brief swap
  constexpr friend inline void swap(Arithmetic&& a, Arithmetic&& b) noexcept {
    using std::swap;
    swap(a.value, b.value);
  }

  /// \name Access operator
  ///@{
  constexpr inline T& operator()() & noexcept { return value; }
  constexpr inline T operator()() && noexcept { return value; }
  constexpr inline T operator()() const& noexcept { return value; }
  ///@}

  /// Data (wrapped value):
  T value;
};
///@}

////////////////////////////////////////////////////////////////////////////////
}  // namespace boost
////////////////////////////////////////////////////////////////////////////////

namespace std {

template <class T, class B>
class numeric_limits<boost::Arithmetic<T, B>> : public numeric_limits<T> {
 public:
  static const bool is_specialized = true;
};

}  // namespace std

////////////////////////////////////////////////////////////////////////////////
#endif  // BOOST_UTILITIES_ARITHMETIC_TYPE_ARITHMETIC_TYPE_

Furthermore, there is a to_string overload as well as istream / ostream operators in other header files. The rationale is, those who don't need them, shouldn't pay for <string>/<istream>/<ostream>.

\$\endgroup\$
  • \$\begingroup\$ I am no expert on arithmetic types so I might be mistaken, but wouldn't it be better to forward each operator to the exact same one on the underlying type? \$\endgroup\$ – Nobody Mar 18 '14 at 13:05
  • 1
    \$\begingroup\$ What is the class B = void template parameter used for? I don't see B being used anywhere. \$\endgroup\$ – ChrisW Mar 18 '14 at 13:14
  • \$\begingroup\$ @ChrisW I've added two examples that show how the class should be used. \$\endgroup\$ – gnzlbg Mar 18 '14 at 13:27
  • \$\begingroup\$ @Nobody if the type models the arithmetic concept this is not needed for all operators, just for some. \$\endgroup\$ – gnzlbg Mar 18 '14 at 13:49
5
\$\begingroup\$

Here are some small things you can improve:

  • constexpr inline isn't necessary: constexpr already implies inline.
  • In the following function:

    constexpr friend inline Arithmetic operator+(Arithmetic a,
                                                 const Arithmetic& b) noexcept {
        return a += b;
    }
    

    The friend keyword isn't useful: the only function you are calling is operator+= which is public. This note holds for all your operator@= and for all the operators that do not use private members.

  • operator<=, operator> and operator>= are generally implemented in function of operator<. A decent level of optimization should get rid of what you seem to consider a overhead.
  • The following member should be constexpr, not const:

    static const bool is_specialized = true;
    
\$\endgroup\$
  • 1
    \$\begingroup\$ Thanks for the answer! Made all the improvements already. Since value is a public data member, I guess I can make all operators that do not need to be class members non-friend non-member functions. \$\endgroup\$ – gnzlbg Mar 18 '14 at 13:46
  • \$\begingroup\$ @gnzlbg Well, you can do that. I don't know whether value being public is a good idea though; I don't really have an opinion on that question. \$\endgroup\$ – Morwenn Mar 18 '14 at 14:47
  • \$\begingroup\$ Neither do I. It seemed irrelevant so I left it public to avoid complicating the life of those who want to explicitly avoid the type-safety offered by the class. \$\endgroup\$ – gnzlbg Mar 18 '14 at 14:59
  • \$\begingroup\$ @gnzlbg Then, what are the operator()() overloads for? They are kind of redundant if value is already public :/ \$\endgroup\$ – Morwenn Mar 18 '14 at 15:00
  • \$\begingroup\$ They are for lazy people, 3 keystrokes less... no kidding. \$\endgroup\$ – gnzlbg Mar 18 '14 at 15:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.