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.
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.
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.
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.
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.
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.
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.
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.
None yet.
Note: the proposed wording is heavily WIP and merely floating an idea at this point.
To subclause [conv], add a subclause [conv.byte]:
An integer-literal whose value is representible by
unsigned char
can be converted to a prvalue of typestd::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]