March 24, 2017·6 min read·engineeringcppstandardsmemorytypes

C++17 finishes technical work with a distinct byte type

Kona completes the technical work on C++17 and adds std::byte. Its small definition needs object-model wording to make byte storage useful without giving it character or arithmetic semantics.

An unsigned character is often used as a byte of storage, even when the program has no intention of treating it as text or a number. That overlap of roles makes an interface less precise. C++17's final technical work adds std::byte, a distinct type for manipulating the bits in storage. Its definition is small, but making it usable requires the language's object model to recognize what the type means.

Herb Sutter's March 24 report says the committee completed C++17's technical work at Kona, where the meeting ran from February 27 through March 4. The draft moves to final ISO balloting. This is technical completion, not a statement that publication has already happened or that every compiler implements the finished draft.

Sutter identifies std::byte as an addition made at this final stage. Neil MacIntosh's P0298R3, dated March 3, explains the design and its wording. The change is useful precisely because it asks the type system to distinguish storage operations from other operations that character types happen to permit.

I am sympathetic to this sort of C++ abstraction. It states a restriction in the interface without requiring a runtime wrapper or an inheritance hierarchy. The cost of understanding it lies in the language rules and permitted operations, which can be examined directly.

A scoped enumeration supplies the distinct type

The proposal defines std::byte as a scoped enumeration with unsigned char as its underlying type. That gives it a known storage basis while keeping it distinct from the arithmetic and character types. It is not simply another typedef for an unsigned character.

The library supplies bitwise operations, shifts and their compound forms. It also supplies std::to_integer, an explicitly requested conversion to an integral type. Ordinary arithmetic is outside the byte interface. A program that wants to add numerical values must first express the conversion to a numerical type.

This example illustrates the resulting distinction using the C++17 draft facility. It assumes an implementation of the newly adopted library definitions.

#include <cstddef>

constexpr std::byte flags{0x05};
constexpr std::byte mask{0x01};
constexpr std::byte selected = flags & mask;
constexpr unsigned value = std::to_integer<unsigned>(selected);

static_assert(value == 1);

The mask operation stays within the byte type. The last conversion asks for an integer because the program then wants a numerical value. That separation makes the transition visible to readers and to tools inspecting the expression types.

The proposed operators are constexpr and noexcept, with integral constraints on shift arguments and to_integer. Those properties describe how the operations participate in constant evaluation and type checking. They do not add a general policy for every integer value an application might use as a shift count.

Boolean use also requires an explicit decision

The paper points out that a scoped enumeration does not implicitly convert for a Boolean condition. Testing a byte therefore needs an explicit comparison, such as comparing it with a zero byte. The type does not silently acquire the truth-value convention of an integer just because it contains bits.

This is a deliberate limit on convenience. An interface that treats every nonzero byte as true can state that policy in a comparison. Another interface may care about one particular bit or a complete expected byte value. The byte type leaves those interpretations to the expression that uses it.

Initialization uses the C++17 rules for direct-list initialization of the enumeration. The proposal describes values representable by the underlying unsigned character type. An application should not read the example as permission for arbitrary narrowing integer conversions. Choosing a storage type with an explicit range keeps that boundary part of initialization.

The definition also avoids introducing a new language keyword. MacIntosh argues that a library type can provide the desired distinction using facilities already available in C++17. The proposal considered an implementation-defined type alternative, but selected the scoped-enumeration definition after library review. The chosen form gives users a concrete account of the type they receive.

Storage access requires core-language permission

A distinct library type is not enough by itself to inspect arbitrary object representations. The language has rules about the types through which a program may access a stored value. Adding an enumeration with an unsigned-character representation would not automatically grant it every permission associated with unsigned-character access.

P0298R3 therefore changes the relevant language wording as well as adding declarations. Its aliasing-related edits recognize std::byte alongside the specified character types for accessing object storage. That is the part that makes the new vocabulary usable in the low-level operations for which it is intended.

The paper also extends object-model wording about arrays that provide storage for another object. An array of std::byte can play the corresponding storage role when the required lifetime and size conditions hold. These conditions remain important. A byte buffer does not by itself establish that every object fits, has the required alignment or has begun its lifetime there.

This division between library definition and language permission explains why the change needs more than a header-only typedef. The library supplies the name and operators. Core wording gives that name the storage-related status needed by programs that examine representations or manage suitably provided storage.

The proposal's updates to lifetime and indeterminate-value wording also make clear that storage has states beyond an initialized numerical value. The type is intended to represent bits of object storage, and those bits still participate in the language's rules about lifetime and valid use. Making the name more precise does not remove those obligations.

A byte sequence still needs an application format

The proposal deliberately supplies a minimum interface. It does not define a network packet format, an encoding for text or a numerical serialization convention. Those meanings belong to the code that interprets the byte sequence. A distinct type makes it easier to keep that interpretation separate from merely holding or masking the storage.

For a function accepting a byte buffer, the declaration can therefore communicate that it consumes uninterpreted storage until the function assigns a meaning. A character buffer has a different apparent purpose, while an integer array suggests arithmetic elements. The difference is a type-level expression of intent, with conversion required when the operation changes interpretation.

This is useful even where all three representations occupy the same amount of storage. The goal is not to reduce the byte's size or create a new physical memory representation. It is to prevent accidental operations and clarify which ones the interface offers. Equal representation does not make all supported expressions equally appropriate.

The header placement has a compatibility detail of its own. P0298R3 adds wording to prevent the C compatibility header <stddef.h> from introducing a global byte name. The new facility belongs to std through <cstddef>, and the proposal excludes its operation declarations from the corresponding global-name behavior. This matters for existing C++ programs that already use an application identifier named byte. A library addition should specify where its names appear, rather than letting an otherwise routine compatibility include unexpectedly introduce another unqualified name into those programs.

Technical completion fixes the draft's requirements

Sutter describes Kona's main work as resolving national-body review comments. The addition of std::byte sits among those final technical decisions. The report does not describe an open invitation to add further large features before publication. Its stated next step is the formal approval process for the technically finished text.

The same report discusses technical specifications as separate work. Coroutines proceeds toward its international comment ballot, while Modules still needs decisions before that stage. Their progress does not make those facilities part of the completed C++17 draft. Keeping the documents separate prevents a standards status report from becoming an inaccurate compiler feature list.

The distinction also matters for deployment decisions. Completion of the language's technical text gives implementers a target, while a particular compiler and standard library determine which parts a program can actually compile. std::byte needs both its header definition and the relevant compiler semantics. A claim of generic C++ support is less specific than a toolchain's account of this revision.

I consider the type's restraint its practical strength. Code can manipulate stored bits, compare bytes and request an integer conversion without inheriting ordinary character or arithmetic behavior accidentally. Its usefulness also depends on the core-language changes, since a distinct enumeration alone cannot authorize representation access. That combination gives C++17 a small storage abstraction with a specified relationship to the object model, while leaving the interpretation of each byte sequence to the application.