| Document #: | P4421R0 [Latest] [Status] |
| Date: | 2026-10-22 |
| Project: | Programming Language C++ |
| Audience: |
Library Evolution |
| Reply-to: |
Damien Lebrun-Grandie <lebrun-grandie@lanl.gov> Mark Hoemmen <mhoemmen@nvidia.com> Jonathan Wakely <cxx@kayari.org> Matthias Kretz <m.kretz@gsi.de> Tim Song <t.canens.cpp@gmail.com> |
numeric_limitsbool is
excludednumeric_limits on integral types are
disabled for integral typesmax_digits10 deviates from
numeric_limits and is enabled for
integral typesreciprocal_overflow_threshold is
removedThis 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.
Initial revision.
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.
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.
numeric_limitsstd::numeric_limits<T>
is defined for every T, and all of
its members are always present. This causes three recurring problems in
generic code:
std::numeric_limits<int>::infinity()
compiles and returns
0. Nothing
tells the caller that
int has no
infinity, unless they remembered to check
has_infinity.has_*
companion. There is no has_epsilon,
so code that should be enabled only for types with an
epsilon has no way to ask.T.
numeric_limits<T>::min()
is the most negative value for integral types, but the smallest positive
normalized value for floating-point types.
lowest() was
added in C++11 to work around this, yet
min() is
still the first member people reach for, and the resulting code compiles
for every arithmetic type while computing the wrong bound for
floating-point ones.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 disabledThe 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.
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).
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.
numeric_limitsPoll: 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).
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.
bool is
excludedPoll: All numeric traits for
boolshould 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.
numeric_limits on integral types are
disabled for integral typesPoll: 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).
max_digits10 deviates from
numeric_limits and is enabled for
integral typesPoll:
max_digits10should deviate fromnumeric_limitsand yieldsdigits10_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.
reciprocal_overflow_threshold is
removedPoll: Remove
reciprocal_overflow_thresholdfrom 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.
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.
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.
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.
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>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].
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.
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]
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>
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; // freestanding1 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.
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
,
where
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.
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
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
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
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
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.
This paper stands on the work of Walter E. Brown (P0437, P1841), whom we thank for the original design and wording.