Document number: P0539R4
Project: Programming Language C++
Audience: SG6 Numerics, Library Evolution
 
Igor Klevanets <cerevra@yandex.ru>, <cerevra@yandex-team.ru>
Antony Polukhin <antoshkka@gmail.com>, <antoshkka@yandex-team.ru>
 
Date: 2018-05-03

A Proposal to add wide_int Template Class

Significant changes to P0539R3 are marked with blue.

Show deleted lines from P0539R3.

Green lines are notes for the editor or for the SG6/LEWG/LWG that must not be treated as part of the wording.

I. Introduction and Motivation

Current standard provides signed and unsigned int8_t, int16_t, int32_t, int64_t. It is usually enough for every day tasks, but sometimes appears a need in big numbers: for cryptography, IPv6, very big counters etc. Non-standard type __int128 which is provided by gcc and clang illuminates this need. But there is no cross-platform solution and no way to satisfy future needs in even more big numbers.

This is an attempt to solve the problem in a generic way on a library level and provide wording for P0104R0: Multi-Word Integer Operations and Types.

A proof of concept implementation available at: https://github.com/cerevra/int/tree/master/v3.

II. Changelog

Differences with P0539R3:

Differences with P0539R2:

Differences with P0539R1:

Differences with P0539R0:

III. Design and paper limitations

wide_integer is designed to be as close as possible to built-in integral types:

wide_integer is not a metafunction. Such metafunctions are discussed in P0102R0.

wide_integer does not add noexcept to operations that have UB for built-in types. Operations that have UB traditionally are not marked with noexcept by LWG. Attempt to change that behavior may be done in separate paper.

In this proposal we concentrate on the wide_integer class that uses machine words under the cover. Such implementations allow you to get best performance and leave behind some design questions, like "Is (sizeof(wide_integer<X>) == X / CHAR_BITS) or not?".

However, we do not wish to shut the door close for extending the abilities of the wide_integer class. Some users may wish to see unit40_t, or unit48_t.

We insist on interface that allows specifying integers not representable by machine words count. Such extensions of functionality may be discussed in separate papers.

wide_integer mimics the behavior of int. Because of that wide_int<128> * wide_int<128> results in wide_int<128>, not wide_int<256>. There are separate proposals for integers that are elastic (P0828R0) or safe (P0228R0).

Non template aliases for integers of particular width (for example int128_t) are handled in separate paper P0102R0. There was an implicit request from LEWG to remove those aliases from this paper, as they were rising a lot of questions on some platforms.

Interoperability with other arithmetic types was moved to a separate paper P0880R0.

We double checked that constexpr on default constructor does not require zero initialization in non constexpr contexts and still allows zero initialization if explicitly asked:

int main() {
     //constexpr wide_integer<128, unsigned> wi;   // Not initialized in constexpr context - compile time error
     wide_integer<128, unsigned> wi_no_init;       // Not initialized - OK
     constexpr wide_integer<128, unsigned> wi{};   // Zero initialized - OK
}
        

Current revision of the paper mimics behavior of int even in cases that are considered dangerous. It is possible to make wide_integer type more safe by making explicit:

Such change will break the compatibility of wide_integer and int. Consider the case, when you have some template function foo(Arithmetic a) that works with arithmetic types. Function is huge and it was developed a long time ago. If interface of wide_integer is same as the interface of int then foo() could work with it. But if we make some of the conversions explicit, then foo() function must be adjusted.

Also note that adding explicit would affect only wide_integers, while there could be a better solution for all the integral types. For example all the integrals could be fixed by some safe_integer<Integral> class, that is very explicit and does additional checks on demand. In that case adding restrictions into wide_integer would just break the interface compatibility with int without big benefit.

IV. Proposed wording

[Note:

Here and below we rely on Arithmetic and Integral that are defined in P0880 in [numeric.requirements] as:

Functions that accept template parameters starting with Arithmetic shall not participate in overload resolution unless std::numeric_limits<Arithmetic>::is_specialized is true.

Functions that accept template parameters starting with Integral shall not participate in overload resolution unless std::numeric_limits<Integral>::is_integer is true.

- end note]

18.3.4 Class template numeric_limits[numeric.limits]

[Note to editor: Add the following sentence after the sentence "Specializations shall be provided for each arithmetic type, both floating-point and integer, including bool." (first sentence in fourth paragraph in [numeric.limits]) - end note]

Specializations shall be also provided for wide_integer type. [Note: If there is a built-in integral type Integral that has the same signedness and width as wide_integer<Bits, S>, then numeric_limits<wide_integer<Bits, S>> specialized in the same way as numeric_limits<Integral>- end note]

26.??.1 Header <wide_integer> synopsis[numeric.wide_integer.syn]

namespace std {

  // 26.??.2 class template wide_integer
  template<size_t Bits, typename S> class wide_integer;

  // 26.??.?? type traits specializations
  template<size_t Bits, typename S, size_t Bits2, typename S2>
  struct common_type<wide_integer<Bits, S>, wide_integer<Bits2, S2>>;

  template<size_t Bits, typename S, typename Arithmetic>
  struct common_type<wide_integer<Bits, S>, Arithmetic>;

  template<typename Arithmetic, size_t Bits, typename S>
  struct common_type<Arithmetic, wide_integer<Bits, S>>
    : common_type<wide_integer<Bits, S>, Arithmetic>
  ;

  // 26.??.?? unary operations
  template<size_t Bits, typename S> constexpr wide_integer<Bits, S> operator~(const wide_integer<Bits, S>& val) noexcept;
  template<size_t Bits, typename S> constexpr wide_integer<Bits, S> operator-(const wide_integer<Bits, S>& val) noexcept(is_unsigned_v<S>);
  template<size_t Bits, typename S> constexpr wide_integer<Bits, S> operator+(const wide_integer<Bits, S>& val) noexcept(is_unsigned_v<S>) noexcept;

  // 26.??.?? binary operations
  template<size_t Bits, typename S, size_t Bits2, typename S2>
    common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
    constexpr operator*(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs);

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
    constexpr operator/(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs);

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
    constexpr operator+(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept(is_unsigned_v<S>);

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
    constexpr operator-(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept(is_unsigned_v<S>);

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
    constexpr operator%(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs);

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
    constexpr operator&(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
    constexpr operator|(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
    constexpr  operator^(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;

  template<size_t Bits, typename S>
    common_type_t<wide_integer<Bits, S>, size_t>
    constexpr  operator<<(const wide_integer<Bits, S>& lhs, size_t rhs);

  template<size_t Bits, typename S>
    common_type_t<wide_integer<Bits, S>, size_t>
    constexpr  operator>>(const wide_integer<Bits, S>& lhs, size_t rhs);

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    constexpr bool operator<(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    constexpr bool operator>(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    constexpr bool operator<=(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    constexpr bool operator>=(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    constexpr bool operator==(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    constexpr bool operator!=(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;

  // 26.??.?? numeric conversions
  template<size_t Bits, typename S> std::string to_string(const wide_integer<Bits, S>& val);
  template<size_t Bits, typename S> std::wstring to_wstring(const wide_integer<Bits, S>& val);

  // 26.??.?? iostream specializations
  template<class Char, class Traits, size_t Bits, typename S>
  basic_ostream<Char, Traits>& operator<<(basic_ostream<Char, Traits>& os, const wide_integer<Bits, S>& val);

  template<class Char, class Traits, size_t Bits, typename S>
  basic_istream<Char, Traits>& operator>>(basic_istream<Char, Traits>& is, wide_integer<Bits, S>& val) noexcept;

  // 26.??.?? hash support
  template<class T> struct hash;
  template<size_t Bits, typename S> struct hash<wide_integer<Bits, S>>;


  template <size_t Bits, typename S>
  to_chars_result to_chars(char* first,
                           char* last,
                           const wide_integer<Bits, S>& value,
                           int base = 10);

  template <size_t Bits, typename S>
  from_chars_result from_chars(const char* first,
                               const char* last,
                               wide_integer<Bits, S>& value,
                               int base = 10);

  template <size_t Bits>
  using wide_int = wide_integer<Bits, signed>;

  template <size_t Bits>
  using wide_uint = wide_integer<Bits, unsigned>

[Note: P0102R0 proposal with exact_2int, fast_2int, least_2int, exact_2uint, fast_2uint, least_2uint requires additional specializations for wide_integer. - end note]

  // optional literals
  inline namespace literals {
  inline namespace wide_int_literals {

  constexpr wide_int<128> operator "" _int128(const char*);
  constexpr wide_int<256> operator "" _int256(const char*);
  constexpr wide_int<512> operator "" _int512(const char*);
  constexpr wide_uint<128> operator "" _uint128(const char*);
  constexpr wide_uint<256> operator "" _uint256(const char*);
  constexpr wide_uint<512> operator "" _uint512(const char*);

  } // namespace wide_int_literals
  } // namespace literals
}

The header <wide_integer> defines class template wide_integer and a set of operators for representing and manipulating integers of specified width.

[Example:
    using int128_t = wide_int<128>;
    constexpr int128_t c = std::numeric_limits<int128_t>::min();
    static_assert(c == 0x80000000000000000000000000000000_uint128);

    int256_t a = 13;
    a += 0xFF;
    a *= 2.0;
    a -= 12_int128;
    assert(a > 0);
]

26.??.2 Template class wide_integer overview[numeric.wide_integer.overview]

namespace std {
  template<size_t Bits, typename S>
  class wide_integer {
  public:
    // 26.??.2.?? construct:
    constexpr wide_integer() noexcept = default;
    constexpr wide_integer(const wide_integer<Bits, S>& ) noexcept = default;
    template<typename Arithmetic> constexpr wide_integer(const Arithmetic& other) noexcept;
    template<size_t Bits2, typename S2> constexpr wide_integer(const wide_integer<Bits2, S2>& other) noexcept;

    // 26.??.2.?? assignment:
    constexpr wide_integer<Bits, S>& operator=(const wide_integer<Bits, S>& ) noexcept = default;
    template<typename Arithmetic>
    constexpr wide_integer<Bits, S>& operator=(const Arithmetic& other) noexcept;
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator=(const wide_integer<Bits2, S2>& other) noexcept;

    // 26.??.2.?? compound assignment:
    template<typename Arithmetic>
    constexpr wide_integer<Bits, S>& operator*=(const Arithmetic&);
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator*=(const wide_integer<Bits2, S2>&);

    template<typename Arithmetic>
    constexpr wide_integer<Bits, S>& operator/=(const Arithmetic&);
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator/=(const wide_integer<Bits2, S2>&);

    template<typename Arithmetic>
    constexpr wide_integer<Bits, S>& operator+=(const Arithmetic&) noexcept(is_unsigned_v<S>);
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator+=(const wide_integer<Bits2, S2>&) noexcept(is_unsigned_v<S>);

    template<typename Arithmetic>
    constexpr wide_integer<Bits, S>& operator-=(const Arithmetic&) noexcept(is_unsigned_v<S>);
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator-=(const wide_integer<Bits2, S2>&) noexcept(is_unsigned_v<S>);

    template<typename Integral>
    constexpr wide_integer<Bits, S>& operator%=(const Integral&);
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator%=(const wide_integer<Bits2, S2>&);

    template<typename Integral>
    constexpr wide_integer<Bits, S>& operator&=(const Integral&) noexcept;
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator&=(const wide_integer<Bits2, S2>&) noexcept;

    template<typename Integral>
    constexpr wide_integer<Bits, S>& operator|=(const Integral&) noexcept;
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator|=(const wide_integer<Bits2, S2>&) noexcept;

    template<typename Integral>
    constexpr wide_integer<Bits, S>& operator^=(const Integral&) noexcept;
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator^=(const wide_integer<Bits2, S2>&) noexcept;

    template<typename Integral>
    constexpr wide_integer<Bits, S>& operator<<=(const Integral&);
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator<<=(const wide_integer<Bits2, S2>&);

    template<typename Integral>
    constexpr wide_integer<Bits, S>& operator>>=(const Integral&) noexcept;
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator>>=(const wide_integer<Bits2, S2>&) noexcept;

    constexpr wide_integer<Bits, S>& operator++() noexcept(is_unsigned_v<S>);
    constexpr wide_integer<Bits, S> operator++(int) noexcept(is_unsigned_v<S>);
    constexpr wide_integer<Bits, S>& operator--() noexcept(is_unsigned_v<S>);
    constexpr wide_integer<Bits, S> operator--(int) noexcept(is_unsigned_v<S>);

    // 26.??.2.?? observers:
    template <typename Arithmetic> constexpr operator Arithmetic() const noexcept;
    constexpr explicit operator bool() const noexcept;

  private:
    byte data[Bits / CHAR_BITS]; // exposition only
  };
}

The class template wide_integer<size_t Bits, typename S> is a trivial standard layout class that behaves as an integer type of a compile time specified bitness.

Template parameter Bits specifies exact bits count to store the integer value. Bits % (sizeof(int) * CHAR_BITS) is eqaul to 0. sizeof(wide_integer<Bits, unsigned>) and sizeof(wide_integer<Bits, signed>) are required to be equal to Bits * CHAR_BITS.

When size of wide_integer is equal to a size of builtin integral type then the alignment and layout of that wide_integer is equal to the alignment and layout of the builtin type.

Template parameter S specifies signedness of the stored integer value and is either signed or unsigned.

Implementations are permitted to add explicit conversion operators and explicit or implicit constructors for Arithmetic and for Integral types.

[Example:

    template <class Arithmetic>
        [[deprecated("Implicit conversions to builtin arithmetic types are not safe!")]]
    constexpr operator Arithmetic() const noexcept;

    explicit constexpr operator bool() const noexcept;
    explicit constexpr operator int() const noexcept;
    ...

— end example ]

26.??.2.?? wide_integer constructors [numeric.wide_integer.cons]

constexpr wide_integer() noexcept = default;
Effects: A Constructs an object with undefined value.
template<typename Arithmetic> constexpr wide_integer(const Arithmetic& other) noexcept;
Effects: Constructs an object from other using the integral conversion rules [conv.integral].
template<size_t Bits2, typename S2> constexpr wide_integer(const wide_integer<Bits2, S2>& other) noexcept;
Effects: Constructs an object from other using the integral conversion rules [conv.integral].

26.??.2.?? wide_integer assignments [numeric.wide_integer.assign]

template<typename Arithmetic>
constexpr wide_integer<Bits, S>& operator=(const Arithmetic& other) noexcept;
Effects: Constructs an object from other using the integral conversion rules [conv.integral].
template<size_t Bits2, typename S2>
constexpr wide_integer<Bits, S>& operator=(const wide_integer<Bits2, S2>& other) noexcept;
Effects: Constructs an object from other using the integral conversion rules [conv.integral].

26.??.2.?? wide_integer compound assignments [numeric.wide_integer.cassign]

template<size_t Bits2, typename S2> constexpr wide_integer<Bits, S>& operator*=(const wide_integer<Bits2, S2>&);
template<size_t Bits2, typename S2> constexpr wide_integer<Bits, S>& operator/=(const wide_integer<Bits2, S2>&);
template<size_t Bits2, typename S2> constexpr wide_integer<Bits, S>& operator+=(const wide_integer<Bits2, S2>&) noexcept(is_unsigned_v<S>);
template<size_t Bits2, typename S2> constexpr wide_integer<Bits, S>& operator-=(const wide_integer<Bits2, S2>&) noexcept(is_unsigned_v<S>);
template<size_t Bits2, typename S2> constexpr wide_integer<Bits, S>& operator%=(const wide_integer<Bits2, S2>&);
template<size_t Bits2, typename S2> constexpr wide_integer<Bits, S>& operator&=(const wide_integer<Bits2, S2>&) noexcept;
template<size_t Bits2, typename S2> constexpr wide_integer<Bits, S>& operator|=(const wide_integer<Bits2, S2>&) noexcept;
template<size_t Bits2, typename S2> constexpr wide_integer<Bits, S>& operator^=(const wide_integer<Bits2, S2>&) noexcept;
template<size_t Bits2, typename S2> constexpr wide_integer<Bits, S>& operator<<=(const wide_integer<Bits2, S2>&);
template<size_t Bits2, typename S2> constexpr wide_integer<Bits, S>& operator>>=(const wide_integer<Bits2, S2>&) noexcept;
constexpr wide_integer<Bits, S>& operator++() noexcept(is_unsigned_v<S>);
constexpr wide_integer<Bits, S> operator++(int) noexcept(is_unsigned_v<S>);
constexpr wide_integer<Bits, S>& operator--() noexcept(is_unsigned_v<S>);
constexpr wide_integer<Bits, S> operator--(int) noexcept(is_unsigned_v<S>);
Effects: Behavior of the above operators is similar to operators for built-in integral types.
template<typename Arithmetic> constexpr wide_integer<Bits, S>& operator*=(const Arithmetic&);
template<typename Arithmetic> constexpr wide_integer<Bits, S>& operator/=(const Arithmetic&);
template<typename Arithmetic> constexpr wide_integer<Bits, S>& operator+=(const Arithmetic&) noexcept(is_unsigned_v<S>);
template<typename Arithmetic> constexpr wide_integer<Bits, S>& operator-=(const Arithmetic&) noexcept(is_unsigned_v<S>);
Effects: As if an object wi of type wide_integer<Bits, S> was created from input value and the corresponding operator was called for *this and the wi.
template<typename Integral> constexpr wide_integer<Bits, S>& operator%=(const Integral&);
template<typename Integral> constexpr wide_integer<Bits, S>& operator&=(const Integral&) noexcept;
template<typename Integral> constexpr wide_integer<Bits, S>& operator|=(const Integral&) noexcept;
template<typename Integral> constexpr wide_integer<Bits, S>& operator^=(const Integral&) noexcept;
template<typename Integral> constexpr wide_integer<Bits, S>& operator<<=(const Integral&);
template<typename Integral> constexpr wide_integer<Bits, S>& operator>>=(const Integral&) noexcept;
Effects: As if an object wi of type wide_integer<Bits, S> was created from input value and the corresponding operator was called for *this and the wi.

26.??.2.?? wide_integer observers [numeric.wide_integer.observers]

template <typename Arithmetic> constexpr operator Arithmetic() const noexcept;
Returns: If Arithmetic type is an integral type then it is constructed from *this using the integral conversion rules [conv.integral]. Otherwise Arithmetic is constructed from *this using the floating-integral conversion rules [conv.fpint].
constexpr explicit operator bool() const noexcept;
Returns: true if *this is not equal to 0.
Returns: If is_integral_v<Arithmetic> then Arithmetic is constructed from *this using the integral conversion rules [conv.integral]. If is_floating_point_v<Arithmetic>, then Arithmetic is constructed from *this using the floating-integral conversion rules [conv.fpint]. Otherwise the operator shall not participate in overload resolution.

26.??.?? Specializations of common_type [numeric.wide_integer.traits.specializations]

template<size_t Bits, typename S, size_t Bits2, typename S2>
struct common_type<wide_integer<Bits, S>, wide_integer<Bits2, S2>> {
  using type = wide_integer<max(Bits, Bits2), see below>;
};

The signed template parameter indicated by this specialization is following:

[Note: common_type follows the usual arithmetic conversions design. - end note]

[Note: common_type attempts to follow the usual arithmetic conversions design here for interoperability between different numeric types. Following two specializations must be moved to a more generic place and enriched with usual arithmetic conversion rules for all the other numeric classes that specialize std::numeric_limits- end note]

template<size_t Bits, typename S, typename Arithmetic>
struct common_type<wide_integer<Bits, S>, Arithmetic> {
  using type = see below;
};
template<typename Arithmetic, size_t Bits, typename S>
struct common_type<Arithmetic, wide_integer<Bits, S>>
  : common_type<wide_integer<Bits, S>, Arithmetic>;

The member typedef type is following:

26.??.?? Unary operators [numeric.wide_integer.unary_ops]

template<size_t Bits, typename S> constexpr wide_integer<Bits, S> operator~(const wide_integer<Bits, S>& val) noexcept;
Returns: value with inverted significant bits of val.
template<size_t Bits, typename S> constexpr wide_integer<Bits, S> operator-(const wide_integer<Bits, S>& val) noexcept(is_unsigned_v<S>);
Returns: val *= -1 if S is true, otherwise the result is unspecified.
template<size_t Bits, typename S> constexpr wide_integer<Bits, S> operator+(const wide_integer<Bits, S>& val) noexcept(is_unsigned_v<S>);
Returns: val.

26.??.?? Binary operators [numeric.wide_integer.binary_ops]

In the function descriptions that follow, CT represents common_type_t<A, B>, where A and B are the types of the two arguments to the function.

template<size_t Bits, typename S, size_t Bits2, typename S2>
  common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
  constexpr operator*(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs);
Returns: CT(lhs) *= rhs.
template<size_t Bits, typename S, size_t Bits2, typename S2>
  common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
  constexpr operator/(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs);
Returns: CT(lhs) /= rhs.
template<size_t Bits, typename S, size_t Bits2, typename S2>
  common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
  constexpr operator+(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept(is_unsigned_v<S>);
Returns: CT(lhs) += rhs.
template<size_t Bits, typename S, size_t Bits2, typename S2>
  common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
  constexpr operator-(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept(is_unsigned_v<S>);
Returns: CT(lhs) -= rhs.
template<size_t Bits, typename S, size_t Bits2, typename S2>
  common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
  constexpr operator%(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs);
Returns: CT(lhs) %= rhs.
template<size_t Bits, typename S, size_t Bits2, typename S2>
  common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
  constexpr operator&(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;
Returns: CT(lhs) &= rhs.
template<size_t Bits, typename S, size_t Bits2, typename S2>
  common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
  constexpr operator|(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;
Returns: CT(lhs) |= rhs.
template<size_t Bits, typename S, size_t Bits2, typename S2>
  common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
  constexpr  operator^(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;
Returns: CT(lhs) ^= rhs.
template<size_t Bits, typename S>
  common_type_t<wide_integer<Bits, S>, size_t>
  constexpr operator<<(const wide_integer<Bits, S>& lhs, size_t rhs);
Returns: CT(lhs) <<= rhs.
template<size_t Bits, typename S>
  common_type_t<wide_integer<Bits, S>, size_t>
  constexpr operator>>(const wide_integer<Bits, S>& lhs, size_t rhs) noexcept;
Returns: CT(lhs) >>= rhs.
template<size_t Bits, typename S, size_t Bits2, typename S2>
  constexpr bool operator<(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;
Returns: true if value of CT(lhs) is less than the value of CT(rhs).
template<size_t Bits, typename S, size_t Bits2, typename S2>
  constexpr bool operator>(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;
Returns: true if value of CT(lhs) is greater than the value of CT(rhs).
template<size_t Bits, typename S, size_t Bits2, typename S2>
  constexpr bool operator<=(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;
Returns: true if value of CT(lhs) is equal or less than the value of CT(rhs).
template<size_t Bits, typename S, size_t Bits2, typename S2>
  constexpr bool operator>=(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;
Returns: true if value of CT(lhs) is equal or greater than the value of CT(rhs).
template<size_t Bits, typename S, size_t Bits2, typename S2>
  constexpr bool operator==(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;
Returns: true if significant bits of CT(lhs) and CT(rhs) are the same.
template<size_t Bits, typename S, size_t Bits2, typename S2>
  constexpr bool operator!=(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;
Returns: !(CT(lhs) == CT(rhs)).

26.??.?? Numeric conversions [numeric.wide_integer.conversions]

template<size_t Bits, typename S> std::string to_string(const wide_integer<Bits, S>& val);
template<size_t Bits, typename S> std::wstring to_wstring(const wide_integer<Bits, S>& val);
Returns: Each function returns an object holding the character representation of the value of its argument. All the significant bits of the argument are outputed as a signed decimal in the style [-]dddd.
template <size_t Bits, typename S>
to_chars_result to_chars(char* first,
                       char* last,
                       const wide_integer<Bits, S>& value,
                       int base = 10);

Behavior of wide_integer overload is subject to the usual rules of primitive numeric output conversion functions [utility.to.chars].

template <size_t Bits, typename S>
from_chars_result from_chars(const char* first,
                           const char* last,
                           wide_integer<Bits, S>& value,
                           int base = 10);

Behavior of wide_integer overload is subject to the usual rules of primitive numeric input conversion functions [utility.from.chars].

26.??.?? iostream specializations [numeric.wide_integer.io]

template<class Char, class Traits, size_t Bits, typename S>
basic_ostream<Char, Traits>& operator<<(basic_ostream<Char, Traits>& os, const wide_integer<Bits, S>& val);
Effects: As if by: os << to_string(val).
Returns: os.
template<class Char, class Traits, size_t Bits, typename S>
basic_istream<Char, Traits>& operator>>(basic_istream<Char, Traits>& is, wide_integer<Bits, S>& val);
Effects: Extracts a wide_integer that is represented as a decimal number in the is. If bad input is encountered, calls is.setstate(ios_base::failbit) (which may throw ios::failure ([iostate.flags])).
Returns: is.

26.??.?? Hash support [numeric.wide_integer.hash]

template<size_t Bits, typename S> struct hash<wide_integer<Bits, S>>;

The specialization is enabled (20.14.14). If there is a built-in integral type Integral that has the same typename and width as wide_integer<Bits, S>, and wi is an object of type wide_integer<Bits, S>, then hash<wide_integer<MachineWords, S>>()(wi) == hash<Integral>()(Integral(wi)).

V. Feature-testing macro

For the purposes of SG10 we recommend the feature-testing macro name __cpp_lib_wide_integer.

VI. Acknowledgements

Many thanks to Alex Strelnikov and John McFarlane for sharing useful ideas and thoughts.