Skip to content

Instantly share code, notes, and snippets.

@Eisenwave
Last active September 13, 2023 22:20
Show Gist options
  • Save Eisenwave/8edd5893335b5bb0c37bc4f1d165e686 to your computer and use it in GitHub Desktop.
Save Eisenwave/8edd5893335b5bb0c37bc4f1d165e686 to your computer and use it in GitHub Desktop.
C++26 Proposal Draft - Conversion of integer literals to std::byte

Conversion of integer-literals to std::byte

Abstract

This proposal aims to improve the ergonomics of the std::byte type by allowing the conversion of an integer-literal to std::byte. This would simplify initialization and comparison of std::byte, which would encourage its use, and make it feel more like a fundamental type.

Introduction

C++17 has introduced std::byte as a the canonical byte type. While it models a byte much more accurately than other types such as unsigned char, or let alone uint8_t, it suffers from poor ergonomics.

This is largely because it has been defined as:

enum class byte : unsigned char {};

Thus, std::byte is limited in its ergonomics by what is possible with scoped enumerations. This proposal aims to make std::byte a more ergonomic type by lifting some of those imposed restrictions.

Motivaton

A typical use case of std::byte is the use in input/output applications. For example, we could use it to verify the file signature of an ELF executable:

static constexpr std::array magic = {
    std::byte{0x7F}, std::byte{0x45}, std::byte{0x4C}, std::byte{0x46}
};
if (std::ranges::starts_with(file_data, magic)) // if true, the file is an ELF file

The repetition of std::byte{} is annoying and cannot be easily avoided. We could create a variadic function to construct arrays of std::byte, but this arguably shouldn't be necessary.

Another common operation with bytes is comparison with specific values:

std::byte b = ...;
if (b == std::byte{0x00})

Once again, this leads to unavoidable repetition of std::byte{} unless we introduce additional utility functios.

In summary, initialization and comparison for std::byte has poor ergonomics. This makes the type unattractive to language users. In my personal experience, many C++ users simply fall back to unsigned char or even std::uint8_t, which don't suffer from these issues.

Proposed solution and design considerations

A conversion of an integer-literal to std::byte should be possible. This makes the following code valid:

std::array<std::byte, 4> magic = { 0x7F, 0x45, 0x4C, 0x46 };
if (magic[0] == 0x7F) // true

This would be similar to pointer conversions, where only an integer-literal with value zero can be converted to a pointer.

Initialization using integral constant expressions

This proposal does not aim to make the following code valid:

constexpr int x = 0;
std::byte b = x; // still ill-formed

It would soften up the type safety which std::byte aims to achieve too much if it was possible to initialize it with a constant expression, not just with a literal.

Impact on existing code

Any code which performs overload resolution with std::byte would be impacted:

void foo(std::byte);     // new fancy std::byte overload
void foo(unsigned char); // legacy API

int main() { foo(0); }

The call to foo(0) would be ambiguous because the conversion sequences int -> unsigned char and int -> std::byte have the same length. Previously, only foo(unsigned char) would have been a candiate.

Code of this form is likely uncommon because the user of a foo function would likely already be in posession of an unsigned char or std::byte object, rather than calling it with a literal.

Why not add a user-defined literal?

The most obvious choice would be operator""b. However, this cannot be done because it would break existing hexadecimal literal such as 0xab. operator""y is already used by <chrono> as a year literal, and operator""t feels too far fetched. operator""_b is not reserved for use by the standard library and would also break an established pattern of not using underscores.

In any case, the goal is to make std::byte feel more like a first-class type, and having to use user-defined literals to achieve basic ergonomics is detrimental to that goal.

Why a special case just for std::byte?

I believe that the problem isn't scoped enumerations as a whole, just std::byte. There appears to be little demand for making the use of scoped enumerations more ergonomic in general, but std::byte is a consistent nuisance.

Allowing the initialization of scoped enumerations using integer-literals isn't always appropriate. For example:

enum class color { red, green, blue, orange, purple };
color c = 0;

This code makes no intutive sense. Of course, the restrictions could only be lifted for scoped numerations with a fixed underlying type and with no named constants. However, it is not obvious whether "enumeration conversions" would be universally appropriate in such a case.

An opt-in version of this initialization would require changes to the language syntax and is not within the scope of this proposal.

Implementation experience

None yet.

Proposed wording

Note: the proposed wording is heavily WIP and merely floating an idea at this point.

To subclause [conv], add a subclause [conv.byte]:

Byte conversions       [conv.byte]

An integer-literal whose value is representible by unsigned char can be converted to a prvalue of type std::byte. The value of the result is the value of the integer-literal.

Add an example:

std::byte a = 0;   // OK
std::byte b(0);    // OK
std::byte c = {0}; // OK

void foo(std::byte);
foo(0);            // OK

To table [tab:over.ics.scs] add a row:

Conversion Category Rank Subclause
Byte conversions Conversion Conversion [conv.byte]

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment