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 cvT .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 cvT .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.