C++23 adopts std::expected at its design freeze
std::expected joins the C++ working draft as the design schedule closes to ordinary new proposals. Its value-or-error model makes failure part of a function's return type while preserving explicit choices about access and propagation.
A function that returns an error code separately from its result leaves callers to keep the two channels consistent. C++23's newly adopted std::expected<T, E> puts the alternatives in one return object. The February 7 plenary minutes, N4907, record adoption of P0323R12 into the C++ working paper by 59 votes to one, with eleven abstentions.
This decision occurs at a particular stage of the revision. The C++23 schedule, P1000R4, identifies the first 2022 meeting as the last ordinary point for sending proposals to wording review. Design feature-completeness does not mean every facility has completed wording review or that the standard has been published. It bounds which work is supposed to proceed through the remaining stages.
I find C++ most compelling where its types describe a real ownership or control-flow obligation. RAII is the strongest example, because scope exit gives resource release a defined place. A common value-or-error type is useful on similar terms. It lets a function state that failure is one of its results without assigning an arbitrary sentinel value to the successful type.
The return type owns one alternative
P0323R12 describes expected<T, E> as containing either an expected value of type T or an error of type E. Its model is a discriminated union whose active alternative is part of the object's state. A caller can ask which alternative is present through has_value or an explicit conversion to bool.
The successful and error types can be the same. That rules out deciding which alternative to construct solely from the type of the argument. The interface therefore uses unexpected to identify error construction, while ordinary success construction follows the value type. Explicit construction tags provide additional control when either alternative needs to be built in place.
This example illustrates the adopted interface using a small domain-specific error type. It requires a library implementation of the newly adopted facility.
#include <expected>
enum class count_error { negative };
std::expected<unsigned, count_error> checked_count(int value)
{
if (value < 0)
return std::unexpected(count_error::negative);
return static_cast<unsigned>(value);
}
The error has a name and a type instead of occupying a reserved unsigned value. The function's implementation decides the policy for negative input. expected supplies storage and access rules for representing that policy. It does not prescribe which inputs a particular application must reject.
The template also has no default error-type argument. The paper records a committee decision against selecting a default such as std::error_code. An API designer therefore names the error representation in the signature. That representation may be a compact enumeration or a richer object, according to the interface's requirements.
Checking and accessing have different contracts
The paper offers accessors with different obligations. value() returns the contained value when present and throws bad_expected_access<E> when the object contains an error. The exception carries the stored error. That gives the accessor defined failure behaviour rather than requiring the caller to prove success before calling it.
Dereference has a narrower contract. The paper explicitly says dereferencing an expected object without a value is undefined behaviour. Its rationale is to permit efficient access when the caller has already established the state. error() likewise requires the error alternative to be active.
These differences mean expected does not eliminate the need to reason about control flow. A branch that checks the state establishes which accessor is permitted. Calling an unchecked accessor on the wrong state is still a program error. A vocabulary type makes the alternatives explicit, but the caller must use its interface according to the chosen contract.
That also clarifies the role of exceptions. Returning expected provides an explicit error channel, but value() deliberately connects to exception-based code. The adopted design does not claim that every operation involving the template is nonthrowing. Construction and movement also depend on the types supplied as T and E and on the requirements of the particular operation.
Never empty is a substantive guarantee
P0323R12 guarantees that an expected object has one of its two alternatives constructed, even after an operation on it fails. The user does not have to handle a third valueless state in addition to success and failure. That keeps the state model appropriate for a function result.
Maintaining the guarantee affects which operations are available. The paper applies requirements to the operations that need them and restricts emplacement when construction cannot meet the required nonthrowing condition. This is a design tradeoff. A simpler set of observable states requires constraints on how transitions between them can occur.
Default construction is another deliberate choice. Where the value type supports it, default construction produces a value-initialized T. It does not mean an unknown error or an absent result. The paper compares alternatives and explains that the committee requested this behaviour. Code that needs an error state should construct one explicitly instead of relying on an intuition borrowed from default-constructed optional.
I prefer that distinction to remain visible in API reviews. A type with a success alternative is not automatically appropriate as a placeholder for work that has not yet run. Choosing a default state is part of the application's model, even when the library provides a convenient default constructor.
Errors can be stored before policy is decided
The paper's motivating WebKit example parses parts of an IPv4 address and stores intermediate results. It explains that the first encountered failure is not necessarily the final error returned. Parsing must continue so that the surrounding logic can distinguish outcomes according to its rules.
That example gives a concrete reason to represent errors as values. A result can move through a container or survive until a later decision point without immediately unwinding the stack. The error type records the information the later policy needs. The approach can coexist with exceptions elsewhere in the application.
It also shows why optional answers a different question. An optional object can represent absence, while expected additionally carries the reason that the value is unavailable. If the reason is irrelevant, an error-bearing type may add unnecessary policy. If the caller distinguishes failures, discarding that information into a single empty state makes the interface less expressive.
The adopted paper discusses related proposals for monadic operations separately. Their existence should not be read as proof that every composition helper is part of the facility adopted today. A correct description of this decision needs to follow P0323R12's interface and distinguish it from prospective additions mentioned in the rationale.
The expected<void, E> specialization covers operations that succeed without producing a separate value. Its successful state records completion, while its error state carries the same kind of typed information as other instantiations. The paper deliberately does not include reference-valued expected objects, so a signature must choose a supported representation when it needs to identify an existing object.
Storage is also specified directly. The contained value or error occupies storage within the expected object itself. An implementation cannot allocate the alternative in separate dynamic storage merely to implement the wrapper. That does not prohibit a user-supplied value or error type from managing its own allocation, so the wrapper guarantee must be distinguished from the behaviour of its template arguments.
The schedule closes design before it closes wording
The February meeting also adopts library work including conversion from ranges to containers and several range adaptors. The minutes record separate motions and, for some additions, separate votes after an objection to unanimous consent. Each motion identifies the exact paper revision applied to the working draft.
P1000R4 explains why the schedule separates design completion from wording completion. Its model fixes the release interval and includes work that is ready. Features that need more time can continue toward a later revision. This permits ongoing design without making every unfinished proposal a reason to delay the whole standard.
Exceptions remain possible, but they require explicit procedural agreement as well as technical acceptance. The schedule defines stronger consensus thresholds for handling work considered late. That is different from assuming a popular proposal can simply enter at any time before publication.
For implementers and application authors, today's decision therefore provides a draft target with a defined paper revision. It does not promise immediate availability in every standard library. A compiler's language mode and the library's implementation of <expected> remain separate practical requirements. The standardization result is that C++ now has an adopted common design for returning either a value or a typed error, ready for the remaining wording and review process.