Document number: N2591=08-0101

Alisdair Meredith
Fabien Oram
2008-03-17

Refactoring numeric_limits

The Problems

numeric_limits is a monolithic class template designed for user-extension through template specialization. Unfortunately this means any attempt to extend the interface by the standard renders all existing user specializations invalid. This problem could have been avoided if each trait in numeric_limits had been declared as its own template, such as with the type traits library.

Proposed Solution

Break the monolithic numeric_limits class template apart into a lot of individual trait templates, so that new traits can be added easily. For backwards compatibility, maintain the existing numeric_limits template and require the generic versions of the new traits templates to forward all calls back to the existing interface. This preserves compatibility with existing user specializations.

Remove any extensions to the numeric_limits interface added since 2003, and support those features only through the new interface. This ensures existing user specializations remain valid and complete.

Similarities and Differences with Type Traits

The obvious similarity is that both sets of traits are implemented as metafunctions.

The key difference is semantic - numeric_traits are designed to be customised by users for their own types, where type_traits are reserved to the implementation. To help distinguish the two, numeric_traits are implemented in their own namespace.

Rationale

This proposal remains 100% compatible with existing code, explicit specializations of the full numeric_limits template for user-defined types remains valid and conforming while any code using the original template is guaranteed to get the same answer. It provides a way forward for extensions in the future and a more flexible framework that can be used now.

While there is a valid argument that a type truly is not correctly supported in C++0x until the new numeric_limits APIs are implemented, it is not always possible for a user to update their third party libraries. By moving the customization outside the monolithic template, users can support these libraries without intruding directly into their headers.

Proposed Wording

Ammend the following standard text as highlighted.

18.2.1 Numeric limits

Header <limits> synopsis

namespace std {

  template<class T> class numeric_limits;
  enum float_round_style;
  enum float_denorm_style;

  template<> class numeric_limits<bool>;

  template<> class numeric_limits<char>;
  template<> class numeric_limits<signed char>;
  template<> class numeric_limits<unsigned char>;
  template<> class numeric_limits<char16_t>;
  template<> class numeric_limits<char32_t>;
  template<> class numeric_limits<wchar_t>;

  template<> class numeric_limits<short>;
  template<> class numeric_limits<int>;
  template<> class numeric_limits<long>;
  template<> class numeric_limits<long long>;
  template<> class numeric_limits<unsigned short>;
  template<> class numeric_limits<unsigned int>;
  template<> class numeric_limits<unsigned long>;
  template<> class numeric_limits<unsigned long long>;

  template<> class numeric_limits<float>;
  template<> class numeric_limits<double>;
  template<> class numeric_limits<long double>;

  namespace numeric_traits {
    template<class T> class  min;
    template<class T> class  max;
    template<class T> class  lowest;

    template<class T> class  digits;
    template<class T> class  digits10;
    template<class T> class  max_digits10;
    template<class T> class  is_signed;
    template<class T> class  is_integer;
    template<class T> class  is_exact;
    template<class T> class  radix;
    template<class T> class  epsilon;
    template<class T> class  round_error;

    template<class T> class  min_exponent;
    template<class T> class  min_exponent10;
    template<class T> class  max_exponent;
    template<class T> class  max_exponent10;

    template<class T> class  has_infinity;
    template<class T> class  has_quiet_NaN;
    template<class T> class  has_signaling_NaN;
    template<class T> class  float_denorm_style has_denorm;
    template<class T> class  has_denorm_loss;
    template<class T> class  infinity;
    template<class T> class  quiet_NaN;
    template<class T> class  signaling_NaN;
    template<class T> class  denorm_min;

    template<class T> class  is_iec559;
    template<class T> class  is_bounded;
    template<class T> class  is_modulo;

    template<class T> class  traps;
    template<class T> class  tinyness_before;
    template<class T> class  round_style;
  }
}

18.2.1.1 Class template numeric_limits

namespace std {
  template<class T> class numeric_limits {
  public:
    static constexpr bool is_specialized = false;
    static constexpr T min() throw();
    static constexpr T max() throw();
    static constexpr T lowest() throw();

    static constexpr int digits = 0;
    static constexpr int digits10 = 0;
    static constexpr int max_digits10 = 0;
    static constexpr bool is_signed = false;
    static constexpr bool is_integer = false;
    static constexpr bool is_exact = false;
    static constexpr int radix = 0;
    static constexpr T epsilon() throw();
    static constexpr T round_error() throw();

    static constexpr int min_exponent = 0;
    static constexpr int min_exponent10 = 0;
    static constexpr int max_exponent = 0;
    static constexpr int max_exponent10 = 0;

    static constexpr bool has_infinity = false;
    static constexpr bool has_quiet_NaN = false;
    static constexpr bool has_signaling_NaN = false;
    static constexpr float_denorm_style has_denorm = denorm_absent;
    static constexpr bool has_denorm_loss = false;
    static constexpr T infinity() throw();
    static constexpr T quiet_NaN() throw();
    static constexpr T signaling_NaN() throw();
    static constexpr T denorm_min() throw();

    static constexpr bool is_iec559 = false;
    static constexpr bool is_bounded = false;
    static constexpr bool is_modulo = false;

    static constexpr bool traps = false;
    static constexpr bool tinyness_before = false;
    static constexpr float_round_style round_style = round_toward_zero;
  };

  template<class T> class numeric_limits<const T>;
  template<class T> class numeric_limits<volatile T>;
  template<class T> class numeric_limits<const volatile T>;
}

The default numeric_limits<T> template shall have all members, but with 0 or false values.

The value of each member of a specialization of numeric_limits on a cv-qualified type cv T shall be equal to the value of the corresponding member of the specialization on the unqualified type T.

18.2.1.1 numeric_limits members

static constexpr T min() throw();

Minimum finite value.

For floating types with denormalization, returns the minimum positive normalized value.

Meaningful for all specializations in which is_bounded != false, or is_bounded == false && is_signed == false.

static constexpr T max() throw();

Maximum finite value.

Meaningful for all specializations in which is_bounded != false.

static constexpr T lowest() throw();

A finite value x such that there is no other finite value y where y < x.

Meaningful for all specializations in which is_bounded != false.

static constexpr int digits;

Number of radix digits that can be represented without change.

For integer types, the number of non-sign bits in the representation.

For floating point types, the number of radix digits in the mantissa.

static constexpr int digits10;

Number of base 10 digits that can be represented without change.

Meaningful for all specializations in which is_bounded != false.

static constexpr int max_digits10;

Number of base 10 digits required to ensure that values which differ are always differentiated.

Meaningful for all floating point types.

...

18.2.1.5 numeric_limits specializations

All members shall be provided for all specializations. However, many values are only required to be meaningful under certain conditions (for example, epsilon() is only meaningful if is_integer is false). Any value that is not "meaningful" shall be set to 0 or false.

Example:


namespace std {

  template<> class numeric_limits<float> {
  public:

    static constexpr bool is_specialized = true;

    inline static constexpr float min() throw() { return 1.17549435E-38F; }
    inline static constexpr float max() throw() { return 3.40282347E+38F; }
    inline static constexpr float lowest() throw() { return -3.40282347E+38F; }

    static constexpr int digits = 24;
    static constexpr int digits10 = 6;
    static constexpr int max_digits10 = 9;

    static constexpr bool is_signed = true;
    static constexpr bool is_integer = false;
    static constexpr bool is_exact = false;

    static constexpr int radix = 2;
    inline static constexpr float epsilon() throw() { return 1.19209290E-07F; }
    inline static constexpr float round_error() throw() { return 0.5F; }

    static constexpr int min_exponent = -125;
    static constexpr int min_exponent10 = - 37;
    static constexpr int max_exponent = +128;
    static constexpr int max_exponent10 = + 38;

    static constexpr bool has_infinity = true;
    static constexpr bool has_quiet_NaN = true;
    static constexpr bool has_signaling_NaN = true;
    static constexpr float_denorm_style has_denorm = denorm_absent;
    static constexpr bool has_denorm_loss = false;

    inline static constexpr float infinity() throw() { return ...; }
    inline static constexpr float quiet_NaN() throw() { return ...; }
    inline static constexpr float signaling_NaN() throw() { return ...; }
    inline static constexpr float denorm_min() throw() { return min(); }

    static constexpr bool is_iec559 = true;
    static constexpr bool is_bounded = true;
    static constexpr bool is_modulo = false;
    static constexpr bool traps = true;
    static constexpr bool tinyness_before = true;

    static constexpr float_round_style round_style = round_to_nearest;
  };
}

-end example

The specialization for bool shall be provided as follows:

namespace std {
  template<> class numeric_limits<bool> {
  public:
    static constexpr bool is_specialized = true;
    static constexpr bool min() throw() { return false; }
    static constexpr bool max() throw() { return true; }
    static constexpr bool lowest() throw() { return false; }

    static constexpr int digits = 1;
    static constexpr int digits10 = 0;
    static constexpr int max_digits10 = 0;

    static constexpr bool is_signed = false;
    static constexpr bool is_integer = true;
    static constexpr bool is_exact = true;
    static constexpr int radix = 2;
    static constexpr bool epsilon() throw() { return 0; }
    static constexpr bool round_error() throw() { return 0; }

    static constexpr int min_exponent = 0;
    static constexpr int min_exponent10 = 0;
    static constexpr int max_exponent = 0;
    static constexpr int max_exponent10 = 0;

    static constexpr bool has_infinity = false;
    static constexpr bool has_quiet_NaN = false;
    static constexpr bool has_signaling_NaN = false;
    static constexpr float_denorm_style has_denorm = denorm_absent;
    static constexpr bool has_denorm_loss = false;
    static constexpr bool infinity() throw() { return 0; }
    static constexpr bool quiet_NaN() throw() { return 0; }
    static constexpr bool signaling_NaN() throw() { return 0; }
    static constexpr bool denorm_min() throw() { return 0; }

    static constexpr bool is_iec559 = false;
    static constexpr bool is_bounded = true;
    static constexpr bool is_modulo = false;

    static constexpr bool traps = false;
    static constexpr bool tinyness_before = false;
    static constexpr float_round_style round_style = round_toward_zero;
  };
}

The following text is all new, so to aid clarity no insert or delete markup is used.

18.2.3 Numeric traits

Numeric traits provide an extensible mechanism to describe the properties of value types. This scheme is more extensible than numeric_limits as each trait can be customized separately without specializing the whole template. New traits can be added without invalidating existing templates. For compatiblility, all traits with an analogue in numeric_limits are required to forward to that template.

namespace std {
  namespace numeric_traits {
    template<class T> class  min {
      static constexpr T value() { return numeric_limits<T>::min(); }
    };
    
    template<class T> class  max {
      static constexpr T value() { return numeric_limits<T>::max(); }
    };
    
    template<class T>
    class digits : integral_constant<int, numeric_limits<T>::digits()> {};
    template<class T>
    class max_digits10 : integral_constant<int, numeric_limits<T>::max_digits10()> {};
    template<class T>
    class is_signed : integral_constant<bool, numeric_limits<T>::is_signed()> {};
    template<class T>
    class is_integer : integral_constant<bool, numeric_limits<T>::is_integer()> {};
    template<class T>
    class is_exact : integral_constant<bool, numeric_limits<T>::is_exact()> {};
    template<class T>
    class radix : integral_constant<int, numeric_limits<T>::radix()> {};
    
    template<class T> class  epsilon {
      static constexpr T value() { return numeric_limits<T>::epsilon(); }
    };
    
    template<class T> class  round_error {
      static constexpr T value() { return numeric_limits<T>::round_error(); }
    };

    template<class T>
    class min_exponent : integral_constant<int, numeric_limits<T>::min_exponent()> {};
    template<class T>
    class min_exponent10 : integral_constant<int, numeric_limits<T>::min_exponent10()> {};
    template<class T>
    class max_exponent : integral_constant<int, numeric_limits<T>::max_exponent()> {};
    template<class T>
    class max_exponent10 : integral_constant<int, numeric_limits<T>::max_exponent10()> {};

    template<class T>
    class  has_infinity : integral_constant<bool, numeric_limits<T>::has_infinity()> {};
    template<class T>
    class  has_quiet_NaN : integral_constant<bool, numeric_limits<T>::has_quiet_NaN()> {};
    template<class T>
    class  has_signaling_NaN : integral_constant<bool, numeric_limits<T>::has_signaling_NaN()> {}
    
    template<class T>
    class has_denorm : integral_constant<float_denorm_style, numeric_limits<T>::has_denorm()> {};
    template<class T>
    class has_denorm_loss : integral_constant<bool, numeric_limits<T>::has_denorm_loss()> {};
    
    template<class T> class  infinity {
      static constexpr T value() { return numeric_limits<T>::infinity(); }
    };
    template<class T> class  quiet_NaN {
      static constexpr T value() { return numeric_limits<T>::quiet_NaN(); }
    };
    template<class T> class  signaling_NaN {
      static constexpr T value() { return numeric_limits<T>::signaling_NaN(); }
    };
    template<class T> class  denorm_min {
      static constexpr T value() { return numeric_limits<T>::denorm_min(); }
    };

    template<class T>
    class is_iec559 : integral_constant<bool, numeric_limits<T>::is_iec559()> {};
    template<class T>
    class is_bounded : integral_constant<bool, numeric_limits<T>::is_bounded()> {};
    template<class T>
    class is_modulo : integral_constant<bool, numeric_limits<T>::is_modulo()> {};

    template<class T>
    class traps : integral_constant<bool, numeric_limits<T>::traps()> {};
    template<class T>
    class tinyness_before : integral_constant<bool, numeric_limits<T>::tinyness_before()> {};
    template<class T>
    class round_style : integral_constant<float_round_style, numeric_limits<T>::round_style()> {};
  }
}

18.2.3.1 Namespace numeric_traits[numeric.traits.lowest]

    template<class T> class  lowest {
      static constexpr T value();
    };

Requires: T is a bounded type, such that is_bounded<T>::value() != false.

Returns: value() returns a finite value x such that there is no other finite value y where y < x.

18.2.3.2 Namespace numeric_traits[numeric.traits.digits10]

    template<class T>
    class digits10 : integral_constant<int, value> {};

Requires: T is a bounded type, such that is_bounded<T>::value() != false.

Returns: value shall be the number of base 10 digits that can be represented by T without change.