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 ) { return T ( 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 writemake_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
shows that roughly C++ 4400 files already use
and
.
This is impressive considering that the feature doesn’t exist in the standard library
and naming the functions
is simply a community convention.
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 > [[ nodiscard ]] constexpr std :: make_signed_t < T > to_signed ( T x ) noexcept { return static_cast < std :: make_signed_t < T >> ( x ); } template < class T > [[ nodiscard ]] 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 4400 existing uses of
on GitHub.
5. Proposed wording
The proposed wording is relative to [N4950].
In subclause 17.3.2 [version.syn], add the following feature-testing macro:
#define __cpp_lib_to_signed 20XXXXL // also in <utility>
In subclause 22.2.1 [utility.syn], update the synopsis as follows:
namespace std { [...] // [utility.signconv] 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 22.2 [utility.syn], add a subclause prior to [utility.underlying]:
22.2.8 Sign conversion functions [utility.signconv]template < class T > constexpr make_signed_t < T > to_signed ( T value ) noexcept ; 1 Returns:
.
static_cast < make_signed_t < T >> ( x ) template < class T > constexpr make_unsigned_t < T > to_unsigned ( T value ) noexcept ; 2 Returns:
.
static_cast < make_unsigned_t < T >> ( x )
Note: The name of the subclause is based on [meta.trans.sign], sign modifications.