Numeric Traits for C++29

Document #: P4421R0 [Latest] [Status]
Date: 2026-10-22
Project: Programming Language C++
Audience: Library Evolution
Reply-to: Damien Lebrun-Grandie
<>
Mark Hoemmen
<>
Jonathan Wakely
<>
Matthias Kretz
<>
Tim Song
<>

1 Abstract

This paper revives [P1841R3] (“Wording for Individually Specializable Numeric Traits”) for C++29, folding in the resolutions from [P2551R2] (“Clarify intent of P1841 numeric traits”) that were polled favorably by Library Evolution in 2022 but never turned into a new revision of P1841. It proposes no new design: it updates the wording to reflect the consensus Library Evolution already reached. It also reports several years of production implementation experience with these traits in Kokkos, a widely-deployed C++ performance-portability library, which independently arrived at essentially the same design.

2 Revision History

2.1 Revision 0

Initial revision.

3 Introduction

Individually specializable numeric traits have a long history in WG21:

Library Evolution answered every design question raised in P2551 by straw poll during two 2022 telecons (reproduced in Library Evolution Resolutions below), and P2551R2 proposed corresponding changes to P1841’s wording. However, P1841R3 (February 2022) predates those polls, and no later revision of P1841 was ever published to carry the changes into new wording. The proposal has been inactive since.

In the meantime, Kokkos shipped its own Kokkos::infinity, Kokkos::epsilon, Kokkos::finite_min, and similar traits (see Implementation Experience), arriving independently at a design consistent with P2551’s resolutions. This paper proposes to revive P1841 for a future standard (targeting C++29), applying P2551’s resolutions to the wording, and backing the proposal with that implementation experience.

4 Motivation and Scope

Summary. This paper proposes a set of numeric traits in <numbers> (epsilon, infinity, finite_max, digits, and so on), each a separate class template that is either enabled (it has a value member) or disabled (it does not). Generic numerical code can then ask, at compile time and without a separate has_* query, whether a given value or characteristic exists for a type T, and get an error rather than a meaningless default when it does not. The traits can also be specialized individually for cv-unqualified program-defined types. The motivation is the same as in [P0437R1] and [P1370R1]; this section only summarizes it.

4.1 The problem with numeric_limits

std::numeric_limits<T> is defined for every T, and all of its members are always present. This causes three recurring problems in generic code:

4.2 What the traits change

Each trait is individually specializable and is disabled for any type for which it has no meaning, so misuse is detected at compile time:

template <class T>
  requires requires { std::numbers::epsilon<T>::value; }
bool close(T a, T b, T tol = std::numbers::epsilon<T>::value);

close(1.0, 1.0 + 1e-12);   // OK
close(1, 2);               // error: epsilon<int> is disabled

The names also depart from numeric_limits, as P1841 already did. Each name says what it returns: finite_min and finite_max are the least and greatest finite values of T, and norm_min is the smallest positive normalized value. Because the name carries the meaning, it means the same thing for every T for which the trait is enabled.

4.3 Scope

This is a pure library addition targeting C++29. It does not change or deprecate numeric_limits, and it does not mandate anything about device code. The Library Evolution resolutions applied to the P1841R3 wording are listed in Library Evolution Resolutions. The paper is backed by implementation experience in Kokkos, which has shipped these traits since Kokkos 3.4 (April 2021) and needs its own versions anyway because numeric_limits members are not generally callable from GPU device code (see Implementation Experience).

5 Library Evolution Resolutions

This paper introduces no new design. The traits are those of [P0437R1], [P1370R1], and [P1841R3], which Library Evolution reviewed at length. The questions that remained open were collected in [P2551R2] and settled by Library Evolution straw polls in 2022. The wording in this paper reflects those outcomes, which are listed below together with how each is applied.

5.1 Deviation from numeric_limits

Poll: Numeric traits can deviate from numeric_limits. SF: 13, F: 8, N: 0, A: 0, SA: 0 (LEWG telecon, 2022-03-29)

Applied. The traits are specified directly in terms of a type’s representation, rather than being defined as forwarding to the corresponding numeric_limits member. This is what makes it possible to disable a trait for a type for which the corresponding numeric_limits member would otherwise return a meaningless default (see Motivation and Scope).

5.2 Representation, not run-time behavior, determines enablement

Poll: Numeric traits should be based on representation rather than behavior (ignoring reciprocal_overflow_threshold). SF: 7, F: 5, N: 2, A: 0, SA: 0 (LEWG telecon, 2022-03-29)

Applied. Whether a specialization is enabled, and its value if so, is fixed by the type’s representation alone. It does not depend on run-time state such as a processor’s flush-to-zero mode. This directly answers P2551’s first design question, about whether denorm_min should be affected by a processor unconditionally treating subnormal values as zero: it should not.

5.3 bool is excluded

Poll: All numeric traits for bool should be disabled. SF: 12, F: 6, N: 1, A: 0, SA: 0 (LEWG telecon, 2022-03-29)

Applied. Although bool is technically an arithmetic type (6.9.3 Fundamental types [basic.fundamental]), it is not used as a numeric type in practice, and std::numeric_limits<bool> remains available for the rare code that needs it.

5.4 Traits not meaningful for numeric_limits on integral types are disabled for integral types

Poll: The numeric traits that are not meaningful for numeric_limits (denorm_min, epsilon, etc) should be disabled for integral types. SF: 14, F: 3, N: 0, A: 0, SA: 0 (LEWG telecon, 2022-03-29)

Applied. denorm_min, epsilon, infinity, norm_min, quiet_NaN, round_error, and signaling_NaN are enabled only for floating-point types. finite_min, finite_max, digits, digits10, and radix remain enabled for every arithmetic type other than bool, since they are meaningful for integral types too (and already are on numeric_limits).

5.5 max_digits10 deviates from numeric_limits and is enabled for integral types

Poll: max_digits10 should deviate from numeric_limits and yields digits10_v<T> + 1. SF: 6, F: 5, N: 2, A: 0, SA: 0 (LEWG telecon, 2022-03-29)

Applied, with a caveat. This poll passed with less consensus than the others (13 votes, 2 abstentions, no opposition) and the authors of P2551 have indicated they did not personally agree with the room’s decision here. This paper nonetheless follows the polled resolution: max_digits10 is enabled for every arithmetic type other than bool, and for integral T its value is digits10_v<T> + 1.

5.6 reciprocal_overflow_threshold is removed

Poll: Remove reciprocal_overflow_threshold from P1841. SF: 6, F: 4, N: 1, A: 0, SA: 0 (LEWG telecon, 2022-06-07)

Applied. reciprocal_overflow_threshold was, unlike every other trait in this proposal, specified in terms of run-time behavior (whether T(1) / x overflows), which conflicts with the representation-based design adopted above and cannot be given a single well-defined compile-time value independent of the floating-point environment. It is removed from the proposal entirely, along with its associated variable template.

6 Implementation Experience

Kokkos has shipped individually specializable numeric traits since Kokkos 3.4 (April 2021), implemented from P1841R1. Kokkos needed its own version of numeric_limits for an unrelated reason: its members lack the host and device specifiers required to call them from GPU code. Having to write one anyway, we chose Walter Brown’s design over a device-friendly clone of numeric_limits, because we liked it.

The relevant sentence of P1841R1 ([num.traits.general]) reads:

Not all numeric traits are applicable to all numeric types, but where a numeric trait is applicable, its definition provides a value appropriate to the type’s representation.

Kokkos interpreted this as meaning that a trait is enabled when, and only when, a value exists in the type’s representation, and otherwise has no value member. This is the reading that [P2551R2] later asked to have clarified and that Library Evolution reconfirmed (see Library Evolution Resolutions).

That interpretation has met its goals. In the years since the first release, we have received no report of misuse of these traits, or of confusion about them, that is not already covered by those resolutions. Users have relied on the traits being individually specializable, and on their absence being detectable.

Kokkos does differ from this proposal in two minor ways, both tracked for correction: it does not special-case bool out of the traits enabled for arithmetic types, and it enables max_digits10 only for floating-point types.

7 Impact on the Standard

This is a pure library addition. It adds a new subclause to 17.3 Implementation properties [support.limits], restoring the wording already reviewed by LWG in P1841R3, with the wording changes described in Library Evolution Resolutions applied. It does not change the meaning of any existing, valid program. As with P1841R3, the new templates are proposed to be freestanding.

8 Proposed Wording

All wording is relative to [N5054], the current Working Draft. The new subclause [num.traits] is derived from the wording of [P1841R3], with the resolutions of P2551R2 (see Library Evolution Resolutions) applied. The main branch of the Working Draft sources (https://github.com/cplusplus/draft) as of 2026-10-14 does not differ from N5054 in any passage edited below. Text added to the Working Draft is shown as additions, and text removed from it as removals.

8.1 Feature-test macro

After adjusting yyyymm (below) so as to denote this proposal’s month of adoption, insert the following line in 17.3.2 Header <version> synopsis [version.syn], in alphabetical order between __cpp_lib_null_iterators and __cpp_lib_observable_checkpoint:

#define __cpp_lib_numeric_traits yyyymmL // freestanding, also in <numbers>

8.2 Edit 29.1 General [numerics.general] as shown

2 The following subclauses describe components for complex number types, random number generation, numeric (n-at-a-time) arrays, generalized numeric algorithms, traits for numeric types, and mathematical constants and functions for floating-point types, as summarized in Table [tab:numerics.summary].

8.3 Edit 17.3.1 General [support.limits.general] as shown

1 The headers <limits> (17.3.3 Header <limits> synopsis [limits.syn]), <climits> (17.3.6 Header <climits> synopsis [climits.syn]), and <cfloat> (17.3.7 Header <cfloat> synopsis [cfloat.syn]) supply implementation-dependent characteristics of implementation-dependent arithmetic types (6.9.3 Fundamental types [basic.fundamental]). In addition, the header <numbers> (29.8.1 Header <numbers> synopsis [numbers.syn]) supplies components, collectively known as numeric traits, that not only provide implementation-dependent distinguished values and characteristics for arithmetic types, but that are (via specialization) individually extensible to provide analogous values and characteristics for program-defined types.

8.4 Edit 6.9.3 Fundamental types [basic.fundamental] as shown

14 Integral and floating-point types are collectively termed arithmetic types. [Note: Properties of the arithmetic types, such as their minimum and maximum representable value, can be queried using the facilities in the standard library headers <limits> (17.3.3 Header <limits> synopsis [limits.syn]), <climits> (17.3.6 Header <climits> synopsis [climits.syn]), and<cfloat> (17.3.7 Header <cfloat> synopsis [cfloat.syn]), and <numbers> (29.8.1 Header <numbers> synopsis [numbers.syn]) — end note]

8.5 Edit 16.4.2.5 Freestanding implementations [compliance] as shown

In the row for 17.3 Implementation properties [support.limits] of Table [tab:headers.cpp.fs] (“C++ headers for freestanding implementations”), add <numbers> to the list of headers:

<cfloat>, <climits>, <limits>, <numbers>, <version>

8.6 Edit 29.8.1 Header <numbers> synopsis [numbers.syn] as shown

Add the following declarations at the end of namespace std::numbers in the synopsis of header <numbers>, after the declaration of phi:

  // [num.traits.val], numeric distinguished value traits
  template<class T> struct denorm_min;                                        // freestanding
  template<class T> struct epsilon;                                           // freestanding
  template<class T> struct finite_max;                                        // freestanding
  template<class T> struct finite_min;                                        // freestanding
  template<class T> struct infinity;                                          // freestanding
  template<class T> struct norm_min;                                          // freestanding
  template<class T> struct quiet_NaN;                                         // freestanding
  template<class T> struct round_error;                                       // freestanding
  template<class T> struct signaling_NaN;                                     // freestanding

  template<class T> constexpr auto denorm_min_v = denorm_min<T>::value;       // freestanding
  template<class T> constexpr auto epsilon_v = epsilon<T>::value;             // freestanding
  template<class T> constexpr auto finite_max_v = finite_max<T>::value;       // freestanding
  template<class T> constexpr auto finite_min_v = finite_min<T>::value;       // freestanding
  template<class T> constexpr auto infinity_v = infinity<T>::value;           // freestanding
  template<class T> constexpr auto norm_min_v = norm_min<T>::value;           // freestanding
  template<class T> constexpr auto quiet_NaN_v = quiet_NaN<T>::value;         // freestanding
  template<class T> constexpr auto round_error_v = round_error<T>::value;     // freestanding
  template<class T> constexpr auto signaling_NaN_v = signaling_NaN<T>::value; // freestanding

  // [num.traits.char], numeric characteristics traits
  template<class T> struct digits;                                            // freestanding
  template<class T> struct digits10;                                          // freestanding
  template<class T> struct max_digits10;                                      // freestanding
  template<class T> struct max_exponent;                                      // freestanding
  template<class T> struct max_exponent10;                                    // freestanding
  template<class T> struct min_exponent;                                      // freestanding
  template<class T> struct min_exponent10;                                    // freestanding
  template<class T> struct radix;                                             // freestanding

  template<class T> constexpr auto digits_v = digits<T>::value;               // freestanding
  template<class T> constexpr auto digits10_v = digits10<T>::value;           // freestanding
  template<class T> constexpr auto max_digits10_v = max_digits10<T>::value;   // freestanding
  template<class T> constexpr auto max_exponent_v = max_exponent<T>::value;   // freestanding
  template<class T> constexpr auto max_exponent10_v = max_exponent10<T>::value;// freestanding
  template<class T> constexpr auto min_exponent_v = min_exponent<T>::value;   // freestanding
  template<class T> constexpr auto min_exponent10_v = min_exponent10<T>::value;// freestanding
  template<class T> constexpr auto radix_v = radix<T>::value;                 // freestanding

8.7 Add the following new subclause to clause 17.3 Implementation properties [support.limits], positioned at the discretion of the Project Editor

8.7.1 17.3.x Numeric traits   [num.traits]

8.7.1.1 17.3.x.1 General   [num.traits.general]

1 Each numeric trait specified in this subclause [num.traits] is declared as a class template of the following form:

template<class T> struct TRAIT;

2 Not all numeric traits are applicable to all numeric types. Each specialization of a numeric trait is either enabled or disabled, as described below. A specialization is enabled only if the value described for it in the relevant subclause below exists for T.

3 Whether a specialization is enabled, and the value of an enabled specialization, is determined solely by T’s representation; it does not depend on run-time state, such as the floating-point environment.

4 An enabled specialization of a numeric trait is a trivial class type (11.2 Properties of classes [class.prop]) with no members other than trivial special member functions and a publicly accessible static data member named value that is usable in constant expressions. The type and value of the value member are specified in the relevant subclause below.

5 A disabled specialization of a numeric trait is a trivial class type with no members other than trivial special member functions. TRAIT<T> is disabled for any cv-unqualified type T for which neither the library nor the program provides an explicit or partial specialization of the TRAIT class template.

6 For each numeric trait TRAIT<cv T>, the library provides partial specializations such that TRAIT<cv T> is enabled if and only if TRAIT<T> is enabled. When enabled, TRAIT<cv T>::value is equal to and has the same type as TRAIT<T>::value.

7 For each numeric trait, the library provides an enabled specialization, if and as applicable, for each cv-unqualified arithmetic type (6.9.3 Fundamental types [basic.fundamental]) other than bool, subject to any further restrictions specified in the relevant subclause below; unless specified otherwise, the library provides no other enabled specializations. A program may specialize a numeric trait for any cv-unqualified program-defined type ([defns.prog.def.type]) to which the numeric trait is applicable.

8.7.1.2 17.3.x.2 Numeric distinguished value traits   [num.traits.val]

1 For the traits specified in this subclause, the value member of an enabled specialization has type const remove_cv_t<T>. The library provides enabled specializations of the traits specified in this subclause only for floating-point types (6.9.3 Fundamental types [basic.fundamental]), except that finite_max and finite_min are enabled for every arithmetic type other than bool.

template<class T> struct denorm_min { see below };

2 When enabled, denorm_min<T>::value is T’s minimum positive denormalized value, if any; otherwise, the same value as norm_min_v<T>. [Note: Whether the execution environment’s floating-point operations treat subnormal operands or results as zero at run time has no effect on whether this specialization is enabled, nor on the value of denorm_min_v<T>. — end note]

template<class T> struct epsilon { see below };

3 When enabled, epsilon<T>::value is e−T(1)e-T(1), where ee denotes T’s least value greater than 1.

template<class T> struct finite_max { see below };

4 When enabled, finite_max<T>::value is a finite value x of T such that T has no other finite value y such that y > x.

template<class T> struct finite_min { see below };

5 When enabled, finite_min<T>::value is a finite value x of T such that T has no other finite value y such that y < x.

template<class T> struct infinity { see below };

6 When enabled, infinity<T>::value is T’s positive infinity.

template<class T> struct norm_min { see below };

7 When enabled, norm_min<T>::value is T’s minimum positive normalized value, if T supports subnormal numbers; otherwise, T’s minimum positive value.

template<class T> struct quiet_NaN { see below };

8 When enabled, quiet_NaN<T>::value is a quiet “Not a Number” value of T.

template<class T> struct round_error { see below };

9 When enabled, round_error<T>::value is T’s maximum rounding error.

template<class T> struct signaling_NaN { see below };

10 When enabled, signaling_NaN<T>::value is a signaling “Not a Number” value of type T.

8.7.1.3 17.3.x.3 Numeric characteristics traits   [num.traits.char]

1 For the traits specified in this subclause, the value member of an enabled specialization has type const int. The library provides enabled specializations of digits, digits10, max_digits10, and radix for every arithmetic type other than bool, and of max_exponent, max_exponent10, min_exponent, and min_exponent10 only for floating-point types (6.9.3 Fundamental types [basic.fundamental]).

template<class T> struct digits { see below };

2 When enabled, digits<T>::value is the number of radix_v<T> digits that can be represented without change. If is_integral_v<T> is true, this is the number of non-sign bits in the representation; if is_floating_point_v<T> is true, this is the number of radix_v<T> digits in the mantissa.

template<class T> struct digits10 { see below };

3 When enabled, digits10<T>::value is the number of base 10 digits that can be represented without change.

template<class T> struct max_digits10 { see below };

4 When enabled, max_digits10<T>::value is the number of base 10 digits required to ensure that type T values which differ are always differentiated; for integral T, this is digits10_v<T> + 1.

template<class T> struct max_exponent { see below };

5 When enabled, with r = radix_v<T>, max_exponent<T>::value is the largest positive integer i such that ri−1r^{i-1} is a representable finite value of type T.

template<class T> struct max_exponent10 { see below };

6 When enabled, max_exponent10<T>::value is the largest positive integer i such that 10i10^i is in the range of representable finite type T values.

template<class T> struct min_exponent { see below };

7 When enabled, with r = radix_v<T>, min_exponent<T>::value is the minimum negative integer i such that ri−1r^{i-1} is a representable, normalized (if applicable), finite value of type T.

template<class T> struct min_exponent10 { see below };

8 When enabled, min_exponent10<T>::value is the minimum negative integer i such that 10i10^i is in the range of representable, normalized (if applicable), finite type T values.

template<class T> struct radix { see below };

9 When enabled, radix<T>::value is the base of the representation. If is_floating_point_v<T> is true, it is the base or radix (often 2) of the exponent representation.

9 Acknowledgments

This paper stands on the work of Walter E. Brown (P0437, P1841), whom we thank for the original design and wording.

[N5054] Thomas Köppe. 2026-07-16. Working Draft, Programming Languages — C++.
https://wg21.link/n5054
[P0437R1] Walter E. Brown. 2018-10-07. Numeric Traits for the Standard Library.
https://wg21.link/p0437r1
[P1370R1] Mark Hoemmen, Damien Lebrun-Grandie. 2019-03-10. Generic numerical algorithm development with(out) numeric_limits.
https://wg21.link/p1370r1
[P1841R3] Walter E Brown. 2022-02-14. Wording for Individually Specializable Numeric Traits.
https://wg21.link/p1841r3
[P2551R2] Matthias Kretz, Jonathan Wakely. 2022-06-22. Clarify intent of P1841 numeric traits.
https://wg21.link/p2551r2