1. Introduction
In integer numerics and bit-manipulation code, it is common to implement functionality in terms of the corresponding signed/unsigned type. The most concise form is a function-style cast with a very short type name.
template < class T > T arithmetic_shift_right ( T x , int s ) { return T ( std :: make_signed_t < T > ( x ) >> s ); } template < class T > T wrapping_add ( T x , T y ) { constexpr unsigned to_int_promotion_defense = 0 ; return T ( to_int_promotion_defense + std :: make_unsigned_t < T > ( x ) + std :: make_unsigned_t < T > ( y )); }
However, this is problematic for two reasons:
-
The use of C-style/function-style casts may conflict with the project’s style. When
is used instead, this code becomes substantially more verbose.static_cast -
Repeating the type
violates the DRY (Don’t Repeat Yourself) principle in software design. Nothing guarantees us thatT
is of typex
when writing an expressionT
. In larger code samples, mismatching types and variables is a bug waiting to happen. To be safe, we would have to writestd :: make_signed_t < T > ( x )
. However, now we are repeating the expressionstd :: make_signed_t < decltype ( x ) > ( x )
, so we haven’t fully solved the problem.x
The greater the distance between
and the use of
are, the easier it is to make a mistake.
To solve these issues, this proposal adds the function templates
and
,
which deduce
from
.
This is concise and always uses the correct type.
A GitHub code search for
/ [ ^ a - zA - Z_ ]( to_signed | to_unsigned ) | static_cast < ( typename ) ? ? (( :: ) ? std :: ) ? ( make_signed | make_unsigned ) / - is : fork language : c ++
... shows that roughly 12.9K C++ files already use a non-standard
and
,
or
to
or
.
By comparison,
/ [ ^ a - zA - Z_ ]( to_underlying ) | static_cast < ( typename ) ? ? (( :: ) ? std :: ) ? ( underlying_type ) / - is : fork language : c ++
... yields 30.8K C++ files which use
or
to
,
of which 11.6K C++ files convert via
.
The proposal [P1682R3] for
had similar rationale, and at the time,
the author was only able to discover 1000 search results for
.
2. Impact on the standard
This proposal is a pure library extension.
Note: [ranges.syn] already defines an exposition-only function
, however, this is more powerful than the proposed function
because it operates on
types, not unsigned integer types.
Therefore, the wording in [ranges] remains unaffected.
3. Possible implementation
template < class T > constexpr std :: make_signed_t < T > to_signed ( T x ) noexcept { return static_cast < std :: make_signed_t < T >> ( x ); } template < class T > constexpr std :: make_unsigned_t < T > to_unsigned ( T x ) noexcept { return static_cast < std :: make_unsigned_t < T >> ( x ); }
4. Design decisions
This proposal follows precedent:
Similar to
, the proposed functions are located in
.
The naming scheme is based on
and the search result in § 1 Introduction.
5. Proposed wording
The proposed wording is relative to [N5001].
In subclause [version.syn], add the following feature-testing macro:
#define __cpp_lib_to_signed 20XXXXL // also in <utility>
In subclause [utility.syn], update the synopsis as follows:
namespace std { [...] // [utility.sign.conv], sign conversion template < class T > constexpr make_signed_t < T > to_signed ( T value ) noexcept ; template < class T > constexpr make_unsigned_t < T > to_unsigned ( T value ) noexcept ; // [utility.underlying], to_underlying template < class T > constexpr underlying_type_t < T > to_underlying ( T value ) noexcept ; [...] }
In subclause [utility], add a subclause immediately prior to [utility.underlying]:
Sign conversion [utility.sign.conv]template < class T > constexpr make_signed_t < T > to_signed ( T value ) noexcept ; Constraints:
is an integral or enumeration type other than cv
T .
bool Returns:
.
static_cast < make_signed_t < T >> ( x ) template < class T > constexpr make_unsigned_t < T > to_unsigned ( T value ) noexcept ; Constraints:
is an integral or enumeration type other than cv
T .
bool Returns:
.
static_cast < make_unsigned_t < T >> ( x )
Note: Because
Mandates that
is an integral type,
is arguably not SFINAE-friendly because say,
is ill-formed without being instantiated.
I don’t attempt to fix this; I assume that the above specification "just works".
Note: The name of the subclause is based on [meta.trans.sign], sign modifications.