Concepts for integer types, not integral types
- Document number:
- P3701R0
- Date:
2025-05-19 - Audience:
- CWG, LEWG
- Project:
- ISO/IEC 14882 Programming Languages — C++, ISO/IEC JTC1/SC22/WG21
- Reply-To:
- Jan Schultke <janschultke@gmail.com>
- GitHub Issue:
- wg21.link/P3701R0/github
- Source:
- github.com/Eisenwave/cpp-proposals/blob/master/src/signed-or-unsigned.cow
concept includes
cv-qualified types, character types, and
,
which is overly broad for many uses cases.
More restrictive concepts are introduced,
and editorial changes are made so "integral" and "integer" are distinct terms.
Contents
Introduction
Design
Is this obsoleted by P3003?
Should const int
be an integer type?
Impact on the standard
Wording
Core wording
Library wording
References
1. Introduction
is widely used by the C++ community
as a constraint for numeric code ([GitHubSearch]).
Many of these uses are questionable because
is satisfied by
character types such as
, as well as
,
and cv-qualified versions of all these types.
and
are similarly permissive.
The C++ standard uses "signed or unsigned integer type" as a more appropriate constraint in [numeric.sat.func], [mdspan.extents.overview], and various other subclauses. That name is clunky and needs to be used instead of the obvious "integer type" because "integer" is already synonymous with "integral", and this proposal cleans that up.
For integer types (with new meaning),
signed integer types,
and unsigned integer types,
I propose the corresponding concepts
,
, and
.
The goal is mainly to provide a simple alternative to the
concepts,
which provide more appropriate constraints in user code.
2. Design
The C++ standard currently uses "integer" and "integral" in a surprising way.
includes types that are not signed integers,
includes types that are not unsigned integers,
but "integer type" is a synonym for "integral type"
([basic.fundamental] definition of "integer type").
The really confusing part is that "integral" is only sometimes, but not always, a broader term than "integer". This can be improved with the following strategy:
Defined term (if any) |
|
Meaning |
---|---|---|
integral type |
|
Lots of stuff: , , , ... |
N/A |
|
integral types which satisfy
|
N/A |
|
integral types which do not satisfy
|
|
||
integer type |
|
signed or unsigned integer types |
signed integer type |
|
, , , etc.(also extended signed integers) |
unsigned integer type |
|
, , , etc.(also extended unsigned integers) |
and
are not integer types, but integral types.
Some alternative designs instead of
were considered:
-
would be inappropriate becausestd :: signed_or_unsigned
isstd :: is_signed_v < float >
.true -
is comically verbose. It would also perpetuate the "signed or unsigned integer" workaround in wording; we ought to have a simpler name.std :: signed_or_unsigned_integer -
,std :: real_integer
,std :: actual_integer
, and other such "integers but like, for real this time" names aren't any less confusing thanstd :: integral2
.std :: integer
2.1. Is this obsoleted by P3003?
[P3003] goes into a different direction: creating a whole library of numeric concepts, which would also include user-defined types (via opt-in).
However, a lot of numeric user code would not be robust enough to take any type
that behaves like an integer mathematically.
User code that is currently constrained with
likely makes some assumptions about
, like
- expressions of type
can be used in aT
, as an array index, etc.,switch
is trivially copyable and usually small,T
is default-constructible,T - no operation can throw, terminate, etc.,
can be converted to floating-point types, among other things,T - ...
is a quick way to constrain a function while documenting these assumptions,
which is needed in practice.
2.2. Should const int
be an integer type?
Unlike
and
,
(and "integer type") would not include cv-qualified types.
Surprisingly,
is not a standard integer or signed integer type,
but
is a standard floating-point type.
There is an obvious inconsistency here, and we need to decide on a direction.
I argue that
should not be an integer type.
While that may be counter-intuitive at first glance,
this behavior is tremendously more useful:
-
A substantial amount of wording (§4. Wording) can be simplified to just
"is of integer type" or "models
". This would need to be qualified with "cv-unqualified" otherwise.integer -
When the user constrains a numeric function template so it only accepts integer types,
they are not interested in supporting
. Neither is the standard library. If the template parameter is deduced from aconst int
function parameter, the only way to provideT x
is explicitly anyway; it never happens organically through deduction. In the extremely unlikely event they want to support it, they should do so explicitly viaT = const int
.std :: remove_cv_t -
It is extremely unlikely that the user intended to support
in their types and functions, so this should require opt-in, not opt-out.volatile int
would be opt-in. Opt-out would be the natural consequence of including cv-qualified types in "integer type".std :: integer < std :: remove_volatile_t < T >> -
Every occurrence of "signed integer type" and "unsigned integer type"
should not include cv-qualified types.
For example, it would be insane if the
of a container could besize_type
.const -
The standard needs to say "cv-unqualified floating-point type(s)"
in a number of places ([N5008]),
and when it doesn't say "cv-unqualified",
it's not always obvious whether that is accidental or by intention.
For example, [basic.extended.fp] never says "cv-unqualified",
but we obviously don't want
. Generally, including cv-qualified types in the definitions by default may be more concise in some places, but it's making the standard less expressive; it leaves intent unexpressed.using float32_t = const _Float32 ;
In conclusion,
being considered an integer may feel philosophically right,
but it's not very useful in wording, and it's not beneficial to
constraints.
being a floating-point type is a bad status quo that we should not perpetuate.
3. Impact on the standard
Firstly, the three new concepts
,
, and
are added.
Secondly, we make editorial changes so that "integer" is consistently a narrower term, and "integral" is a broader term. See §2. Design.
While the changes are all very simple, the wording impact is quite immense because "integer" and "integral" have been used interchangeably throughout wording. Any existing uses of "integer" are replaced with "integral", and the "signed or unsigned integer" pattern can be simplified to just "integer".
4. Wording
The following changes are relative to [N5008].
4.1. Core wording
Change [basic.fundamental] paragraph 5 as follows:
Each value of an unsigned integer type with width has
a unique representation ,
where each coefficient is either 0 or 1;
this is called the base-2 representation of .
The base-2 representation of a value of signed integer type is
the base-2 representation of the congruent value
of the corresponding unsigned integer type.
The standard signed integer types and standard unsigned integer types
are collectively called the standard integer types, and the extended
signed integer types and extended
unsigned integer types are collectively called the
extended integer types.
The standard integer types and extended integer types
are collectively called integer types.
Change [basic.fundamental] paragraph 6 as follows:
A fundamental type specified to have
a signed or unsigned an integer type as its underlying type has
the same object representation,
value representation,
alignment requirements ([basic.align]), and
range of representable values as the underlying type.
Further, each value has the same representation in both types.
Change [basic.fundamental] paragraph 8 as follows:
Type
is a distinct type that has
an implementation-defined
signed or unsigned integer type as its underlying type.
Change [basic.fundamental] paragraph 11 as follows:
The types
,
,
,
, and
are collectively called character types.
The character types,
integer types,
,
the signed and unsigned integer types,
and cv-qualified versions ([basic.type.qualifier]) thereof,
are collectively termed called integral types.
A synonym for integral type is integer type.
Change [conv.rank] paragraph 1 as follows:
Every integer type integral type
has an integer integral conversion rank defined as follows:
- […]
-
The rank of any extended signed integer type
relative to another extended signed integer type
with the same width is implementation-defined,
but still subject to the other rules
for determining the
integerintegral conversion rank. -
For all
integerintegral types
,T1
, andT2
, ifT3
has greater rank thanT1
, andT2
has greater rank thanT2
, thenT3
has greater rank thanT1
.T3
integer integral conversion rank is used
in the definition of the integral promotion ([conv.prom])
and the usual arithmetic conversions ([expr.arith.conv]).
— end note]
Change [conv.lval] paragraph 3, bullet 5 as follows:
Otherwise, the object indicated by the glvalue is read ([defns.access]).
Let
be the value contained in the object.
If
is an integer integral type,
the prvalue result is
the value of type
congruent ([basic.fundamental]) to
, and
otherwise.
Change [conv.prom] paragraph 2 as follows:
A prvalue that is not a converted bit-field and has an integer integral type other than
,
,
,
, or
whose integer integral conversion
rank ([conv.rank]) is less than the rank of
can be
converted to a prvalue of type
if
can represent
all the values of the source type; otherwise, the source prvalue can be
converted to a prvalue of type
.
Change [conv.prom] paragraph 3 as follows:
A prvalue of an unscoped enumeration type whose underlying type is not
fixed can be converted to a prvalue of the first of the following
types that can represent all the values of the enumeration ([dcl.enum]):
,
,
,
,
, or
.
If none of the types in that
list can represent all the values of the enumeration, a prvalue of an unscoped
enumeration type can be converted to a prvalue of the extended integer type with lowest
integer integral conversion rank ([conv.rank])
greater than the rank of
in which all the values of the enumeration can be represented.
If there are two such extended types,
the signed one is chosen.
Change [conv.integral] paragraph 1 as follows:
A prvalue of an integer integral type
can be converted to a prvalue of another integer integral type.
A prvalue of an unscoped enumeration type
can be converted to a prvalue of an integer integral type.
Change [conv.fpint] paragraph 1 as follows:
A prvalue of a floating-point type can be converted to a prvalue of an
integer integral type.
The conversion truncates;
that is, the fractional part is discarded.
The behavior is undefined if the truncated value cannot be represented
in the destination type.
Change [conv.fpint] paragraph 2 as follows:
A prvalue of an integer integral type
or of an unscoped enumeration type
can be converted to
a prvalue of a floating-point type.
[…]
Change [expr.reinterpret.cast] paragraph 5 as follows:
A value of integral type or enumeration type
can be explicitly converted to a pointer.
A pointer converted to an integer integral type of sufficient size
(if any such exists on the implementation)
and back to the same pointer type
will have its original value ([basic.compound]);
mappings between pointers and integers integral types are otherwise
implementation-defined.
Change [expr.assign] paragraph 2 as follows:
In simple assignment (
),
let
be the result of the right operand;
the object referred to by the left operand is
modified ([defns.access]) by replacing its value
with
or,
if the object is of integer integral type,
with the value congruent ([basic.fundamental]) to
.
Change [dcl.init.list] paragraph 7 as follows:
A narrowing conversion is an implicit conversion
- from a floating-point type to an
integerintegral type, or - […]
-
from an
integerintegral type or unscoped enumeration type to a floating-point type, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted back to the original type, or -
from an
integerintegral type or unscoped enumeration type to anintegerintegral type that cannot represent all the values of the original type, except where […] - […]
Change [dcl.enum] paragraph 11 as follows:
The value of an enumerator or an object of an unscoped enumeration type is
converted to an integer integral type by integral promotion ([conv.prom]).
Change [class.bit] paragraph 4 as follows:
If a value of integral type (other than
) is stored
into a bit-field of width and the value would be representable
in a hypothetical signed or unsigned integer type
with width and the same signedness as the bit-field's type,
the original value and the value of the bit-field compare equal. […]
4.2. Library wording
Change [bitmask.types] paragraph 1 as follows:
Several types defined in [support] through [exec] and [depr] are bitmask types.
Each bitmask type can be implemented
as an enumerated type that overloads certain operators, as an integer integral type,
or as a bitset.
Change [support.types.layout] as follows:
Recommended practice:
An implementation should choose types for
and
whose integer integral conversion ranks ([conv.rank]) are no greater than that of
unless a larger size is necessary to contain all the possible values.
Change [version.syn] paragraph 2 as follows:
<concepts>
<compare>
Change [numeric.limits.general] paragraph 4 as follows:
Specializations shall be provided for each
arithmetic type,
both floating-point and integer integral, including
.
The member
shall be
for all such specializations of
.
Change [numeric.limits.members] paragraph 10 as follows:
For integer integral types,
the number of non-sign bits in the representation.
Change [numeric.limits.members] paragraph 18 as follows:
if the type is integer integral.
Change [numeric.limits.members] paragraph 20 as follows:
if the type uses an exact representation.
All integer integral types are exact,
but not all exact types are integer integral.
For example, rational and fixed-exponent representations are exact
but not integer integral.
Change [numeric.limits.members] paragraph 23 as follows:
For integer integral types,
specifies the base of the representation.167
Change [numeric.limits.members] paragraph 65 as follows:
Meaningful for all floating-point types.
Specializations for integer integral types shall return
.
Change [climits.syn] note 1 as follows:
and
, a macro referring to
an integer integral type
defines a constant whose type is the promoted
type of
([conv.prom]).
— end note]
Change [cstdint.syn] paragraph 1 as follows:
The header integer integral types having specified widths, and
macros that specify limits of integer integral types.
Change [cstdint.syn] paragraph 3 as follows:
All types that use the placeholder
,
,
, or
.
The exact-width types
and
for
,
,
, and
are also optional;
however, if an implementation defines integer integral types
with the corresponding width and no padding bits,
it defines the corresponding
and
correspond to the
and
,
respectively.
— end note]
Change [concepts.syn] as follows:
Change [concepts.arithmetic] as follows:
can be modeled even by types
that are not signed integer types ([basic.fundamental]);
for example
.
is modeled exclusively by
signed integer types.
— end note]
can be modeled even by types
that are not unsigned integer types ([basic.fundamental]);
for example,
.
is modeled exclusively by
unsigned integer types.
— end note]
Change [intseq.intseq] paragraph 1 as follows:
Mandates:
is an integer type models
.
Change [utility.intcmp] paragraph 1 and [utility.intcmp] paragraph 4 as follows:
Mandates:
Both
and are standard integer types or extended integer types ([basic.fundamental])
model
.
Change [utility.intcmp] paragraph 9 as follows:
Mandates:
Both
and are standard integer types or extended integer types ([basic.fundamental])
model
.
Change [forward.list.ops] paragraph 1 as follows:
In this subclause, arguments for a template parameter
named
or
shall meet the corresponding requirements in [algorithms.requirements].
The semantics of
,
where
is an iterator into the list and
is an integer of integral type,
are the same as those of
.
The expression
, where
is an iterator into the list and
is an integer,
means an iterator
such that
is
.
For merge and sort, the definitions and requirements in [alg.sorting] apply.
Change [hive.operations] paragraph 1 as follows:
In this subclause,
arguments for a template parameter
named
or
shall meet the corresponding requirements in [algorithms.requirements].
The semantics of
and
,
where
is an iterator into the
and
is an integer of integral type,
are the same as those of
and
, respectively.
For
, the definitions and requirements in [alg.sorting] apply.
Change [list.ops] paragraph 1 as follows:
In this subclause,
arguments for a template parameter
named
or
shall meet the corresponding requirements in [algorithms.requirements].
The semantics of
,
where
is an iterator into the list and
is an integer of integral type,
are the same as those of
.
The expression
,
where
is an iterator into the list and
is an integer,
means an iterator
such that
is
.
For
and
,
the definitions and requirements in [alg.sorting] apply.
Change [mdspan.extents.overview] paragraph 1 as follows:
Mandates:
IndexType is a signed or unsigned integer typemodels
, andinteger -
each element of
is either equal toExtents
, or is representable as a value of typedynamic_extent
.IndexType
Change [mdspan.sub.strided.slice] paragraph 3 as follows:
Mandates:
Each of the types
,
, and are signed or unsigned integer types, or
models
or
.
Change [mdspan.sub.helpers] paragraph 1 and [mdspan.sub.helpers] paragraph 10 as follows:
Mandates:
is a signed or unsigned integer type
models
.
Change [ranges.syn] paragraph 1 as follows:
Within this Clause,
for an integer-like type
([iterator.concept.winc]),
denotes
if
is an integer integral type;
otherwise, it denotes a corresponding unspecified unsigned-integer-like type
of the same width as
.
For an expression
of type
,
is
explicitly converted to
.
Change [ranges.syn] paragraph 2 as follows:
Also within this Clause,
for an integer-like type
denotes
if
is an integer integral type;
otherwise, it denotes a corresponding unspecified signed-integer-like type
of the same width as
.
Change [numeric.ops.gcd] paragraph 1 and [numeric.ops.lcm] paragraph 1 as follows:
Mandates:
and
both are integer integral types other than
.
Change [numeric.ops.midpoint] paragraph 1 as follows:
Returns:
Half the sum of
and
.
If
is an integer integral type and the sum is odd,
the result is rounded towards
.
In [numeric.sat.func], change paragraphs 2, 4, 6, and 8 as follows:
Constraints:
is a signed or unsigned integer type ([basic.fundamental])
models
.
Change [numeric.sat.cast] paragraph 1 as follows:
Constraints:
and are signed or unsigned integer types ([basic.fundamental])
model
.
Change [charconv.syn] paragraph 1 as follows:
When a function is specified
with a type placeholder of
and all cv-unqualified signed and unsigned integer integer types
in lieu of
Change [format.string.std] paragraph 10 as follows:
If signed or unsigned integer type.
If its value is negative, an exception of type
is thrown.
Change [cmplx.over] paragraph 2, bullet 2 as follows:
Otherwise, if the argument has integer integral type,
then it is effectively cast to
.
Change [cmplx.over] paragraph 3 as follows:
Function template
has additional constexpr overloads sufficient to ensure,
for a call with one argument of type
and
the other argument of type
or
,
both arguments are effectively cast to
,
where
is
if
is an integer integral type and
otherwise.
If
is not well-formed,
then the program is ill-formed.
Change [rand.util.seedseq] paragraph 2 as follows:
Constraints:
is an integer type models
.
Change [rand.util.seedseq] paragraph 4 as follows:
Mandates:
is an integer type models
.
Change [numerics.c.ckdint] paragraph 1 as follows:
Mandates:
Each of the types
,
, and is a cv-unqualified signed or unsigned integer type
models
.
Change [cmath.syn] paragraph 3 as follows:
For each function
with at least one parameter of type
,
the implementation also provides additional overloads sufficient to ensure that,
if every argument corresponding to
a integer integral type are considered to have
the same floating-point conversion rank as
.
If no such floating-point type with the greatest rank and subrank exists,
then overload resolution does not result in
a usable candidate ([over.match.general])
from the overloads provided by the implementation.
Change [simd.ctor] paragraph 7, bullet 2 as follows:
both
and
are integral types and the integer integral
conversion rank ([conv.rank]) of
is greater than the integer integral
conversion rank of
, or
Change the title of [atomics.types.int] as follows:
Specializations for integers integral types