PxxxxR0
std::to_signed, std::to_unsigned

New Proposal,

This version:
https://eisenwave.github.io/cpp-proposals/to-signed-unsigned.html
Author:
Audience:
SG18, LEWG
Project:
ISO/IEC 14882 Programming Languages — C++, ISO/IEC JTC1/SC22/WG21
Source:
eisenwave/cpp-proposals

Abstract

This proposal is to std::make_signed what std::to_underlying was to std::underlying_type.

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:

  1. The use of C-style/function-style casts may conflict with the project’s style. When static_cast is used instead, this code becomes substantially more verbose.

  2. Repeating the type T violates the DRY (Don’t Repeat Yourself) principle in software design. Nothing guarantees us that x is of type T when writing an expression make_signed_t<T>(x). In larger code samples, mismatching types and variables is a bug waiting to happen. To be safe, we would have to write std::make_signed_t<decltype(x)>(x). However, now we are repeating the expression x, so we haven’t fully solved the problem.

The greater the distance between T and the use of std::make_{un}signed are, the easier it is to make a mistake.

To solve these issues, this proposal adds the function templates to_signed(x) and to_unsigned(x) which deduce T from x. This is concise and always uses the correct type.

A GitHub code search for language:c++ /[^a-zA-Z_.](to_unsigned|to_signed) *\(/ shows that roughly C++ 4400 files already use to_unsigned and to_signed. This is impressive considering that the feature doesn’t exist in the standard library and naming the functions to_{un}signed is simply a community convention.

The proposal [P1682R3] for std::to_underlying had similar rationale and at the time, the author was only able to discover 1000 search results for to_underlying.

2. Impact on the standard

This proposal is a pure library extension.

Note: [ranges.syn] already defines an exposition-only function to-unsigned-like, however, this is more powerful than the proposed function because it operates on unsigned-like 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 to_underlying, the proposed functions are located in <utility>. The naming scheme is based on to_underlying and the 4400 existing uses of to_{un}signed 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.

References

Normative References

[N4950]
Thomas Köppe. Working Draft, Standard for Programming Language C++. 10 May 2023. URL: https://wg21.link/n4950

Informative References

[P1682R3]
JeanHeyd Meneide. std::to_underlying. 22 January 2021. URL: https://wg21.link/p1682r3