1. Introduction
Since the changes in [P2587R3] have been accepted into the standard,
is no longer
locale-dependent, but defined to forward its arguments to
like:
string to_string ( T x ) { return format ( "{}" , x ); }
Furthermore,
for any floating-point type and integer type
(though
and
behave specially) forwards to
, as specified in [format.string.std]
However, not every integer type can be used with
, and neither can every floating-point type.
This has become an artificial restriction that makes use of
in generic code more
fragile.
Therefore, I propose expanding
to take any integer type and any floating-point type.
Furthermore,
can be made
for integer types
because it is indirectly defined in terms of
,
which is
for integers since [P2291R3].
2. Motivation and scope
is a frequently used function. [P2587R3] has identified ~11 thousand uses in total.
Compared to
, it has the potential to be a much more lightweight dependency,
and also communicates the intent "convert this to a string" more clearly than
.
That is to say, it is not obsoleted by
, and it deserves attention and care.
2.1. The inconveniences of to_string
In its current state,
has some problems that make it harder to use than necessary:
-
does not explicitly support aliases such asto_string
, and these aliases are not guaranteed to use standard integer types. Therefore, anyone usinguint32_t
is inadvertently relying on implementation details.to_string ( uint32_t ) -
does not have overloads for the extended floating-point types into_string
. This is an artificial restriction because< stdfloat >
must support them, andto_chars
simply forwards toto_string
for standard floating-point types.to_chars -
is not markedto_string
despite no longer depending on locale.constexpr
This proposal seeks to remove these inconveniences.
2.2. The sharp edges of to_string
It should be noted that
has a few surprising, sharp edges:
-
yieldsto_string ( true)
, however,"1"
yieldsformat ( "{}" , true)
."true" -
yieldsto_string ( 'a' )
, however,"97"
yieldsformat ( "{}" , 'a' )
."a"
While I personally dislike this status quo, it is not within the scope of this proposal to alter
the existing behavior.
If the user prefers a different behavior for
and
, they can use a different form
of formatting.
has never been a fully-fledged customization point for stringification, only a
function which converts a handful of types.
For a general stringification customization point, the user must either use
, or wrap
in some other function.
Changing the behavior of
would not obsolete this, so it is very difficult to justify.
It should be decided whether semantic changes to
and
are worth pursuing.
3. Impact on the standard
The overload set of
would be altered as follows:
string to_string ( int val ); string to_string ( unsigned val ); string to_string ( long val ); string to_string ( unsigned long val ); string to_string ( long long val ); string to_string ( unsigned long long val ); string to_string ( float val ); string to_string ( double val ); string to_string ( long double val ); constexpr string to_string ( /* integer type >= int */ ); string to_string ( /* floating-point type */ );
(Analogous for
)
Note: In the original overload set, the behavior described in § 2.2 The sharp edges of to_string is a consequence of
and
calling
.
No existing well-formed code is made invalid, and the behavior of existing calls to
is not altered.
This proposal only adds additional overloads for extended integer types and extended floating-point
types.
4. Implementation experience
libstdc++ already implements
as an inline function which uses the
function
.
Similarly, overloads for other integer types and floating-point types are inline functions which
rely on a
-like implementation.
Making
requires the addition of
, but no major
changes to existing code are necessary.
This demonstrates the feasibility of implementing this proposal.
libc++ is most significantly affected because
is not yet an inline function.
This ABI change can be mitigated with
.
5. Design decisions
The overload set is altered so that the lowest minimal changes to
are made.
Notably:
-
remains a non-template function.to_string -
does not receive overloads forto_string
orshort
, but relies on promotion tosigned char
, as in the original design.int
The design strategy in this proposal mirrors that in [P1467R9], which expanded the set
,
,
to
in a similar way.
5.1. constexpr
challenges
There is no obstacle that would make
unimplementable for any type,
at the time of writing.
However, there is the odd issue that
is defined in terms of
(which is not
), which is defined in terms of
(which is
).
This requires awkward wording which "magically" bridges this gap.
This is preferable to re-defining
in terms of
directly because presumably,
will be
sooner or later.
We can then simply remove the bridge wording.
6. Proposed wording
The proposed changes are relative to the working draft of the standard as of [N4917], after additionally applying the changes described in [P2587R3].
Update subclause 17.3.2 [version.syn], paragraph 2 as follows:
#define __cpp_lib_to_string 202306L 20XXXXL
In subclause 23.4.2 [string.syn], update the synopsis as follows:
string to_string ( int val ); string to_string ( unsigned val ); string to_string ( long val ); string to_string ( unsigned long val ); string to_string ( long long val ); string to_string ( unsigned long long val ); string to_string ( float val ); string to_string ( double val ); string to_string ( long double val ); constexpr to_string ( integer - type - least - int val ); to_string ( floating - point - type val ); [...]
wstring to_wstring ( int val ); wstring to_wstring ( unsigned val ); wstring to_wstring ( long val ); wstring to_wstring ( unsigned long val ); wstring to_wstring ( long long val ); wstring to_wstring ( unsigned long long val ); wstring to_wstring ( float val ); wstring to_wstring ( double val ); wstring to_wstring ( long double val ); constexpr to_wstring ( integer - type - least - int val ); to_wstring ( floating - point - type val );
In subclause 23.4.2 [string.syn], add a paragraph:
For each function with a parameter of type integer-type-least-int,
the implementation provides an overload for each cv-unqualified
integer type ([basic.fundamental]) whose conversion rank is that of int
or greater.
For each function with a parameter of type floating-point-type,
the implementation provides an overload for each cv-unqualified
floating-point type.
Update subclause 23.4.5 [string.conversions] as follows:
string to_string ( int val ); string to_string ( unsigned val ); string to_string ( long val ); string to_string ( unsigned long val ); string to_string ( long long val ); string to_string ( unsigned long long val ); string to_string ( float val ); string to_string ( double val ); string to_string ( long double val ); constexpr to_string ( integer - type - least - int val ); to_string ( floating - point - type val ); Returns:
.
format ( "{}" , val ) Remarks: Despite
not being marked
format , the call to
constexpr does not disqualify a call to
format from being a constant expression.
to_string [...]
wstring to_wstring ( int val ); wstring to_wstring ( unsigned val ); wstring to_wstring ( long val ); wstring to_wstring ( unsigned long val ); wstring to_wstring ( long long val ); wstring to_wstring ( unsigned long long val ); wstring to_wstring ( float val ); wstring to_wstring ( double val ); wstring to_wstring ( long double val ); constexpr to_wstring ( integer - type - least - int val ); to_wstring ( floating - point - type val ); Returns:
.
format ( L"{}" , val ) Remarks: Despite
not being marked
format , the call to
constexpr does not disqualify a call to
format from being a constant expression.
to_wstring