C++23 publishes a standard vocabulary for value-or-error results
ISO/IEC 14882:2024 publishes the revision known as C++23. std::expected makes an error alternative part of a function's result type while leaving recovery and exception policy to the interface designer.
A function that returns a value on success and an error on failure needs a representation that keeps those alternatives distinct. An integer sentinel can collide with a valid result, while a separate error output can become detached from the value it describes. C++23 supplies std::expected as a standard vocabulary type for this arrangement. The revision is published as ISO/IEC 14882:2024, with a publication milestone of 19 October. Its publication gives a formal reference to facilities that implementations have been delivering individually.
My strongest argument for C++ remains RAII, with templates providing another substantial benefit when the abstraction has no unnecessary runtime layer. A value-or-error type fits that style of programming. It can own whichever alternative is active while making the caller's next decision visible in ordinary control flow.
The result contains one of two alternatives
The std::expected proposal P0323R12 describes a type containing either an expected value T or an error E. Its design favors value-like use on the success path. An explicit unexpected wrapper identifies construction of the error alternative. That distinction is necessary even when the value and error happen to use the same underlying type.
The advantage over an optional result is information about why no value was produced. An optional value can express absence appropriately when that is the whole outcome. An expected value can attach a typed reason. The choice between them belongs to the API's meaning, rather than a rule that every absent value should become an error.
Here is a small C++23 example. It parses one decimal digit and reports a domain-specific error for other characters.
#include <expected>
enum class digit_error {
invalid_character
};
std::expected<unsigned, digit_error> parse_digit(char input)
{
if (input < '0' || input > '9')
return std::unexpected(digit_error::invalid_character);
return static_cast<unsigned>(input - '0');
}
The error is outside the unsigned result domain. A caller does not need to reserve an otherwise valid number to represent failure. The signature also identifies which error category the implementation promises to report through this result channel. That is useful information before reading the function body.
The example intentionally has a narrow contract. It does not parse a complete integer or describe Unicode numeric characters. A richer parser would need additional format and range rules. The result representation can carry those rules' outcomes, but it does not establish the rules itself.
Handling remains the caller's responsibility
A caller can inspect whether the result contains a value before accessing it. The proposal distinguishes value access that checks and can throw from dereference operations whose use requires the value alternative. Choosing expected therefore does not make unchecked consumption correct. It gives the program a way to ask which alternative is active.
This matters when evaluating claims that an error type forces handling. A function signature makes the alternative visible, but the caller still needs appropriate control flow. Discarding a result or accessing the wrong alternative can remain a bug. A project can add conventions and tooling, while the type supplies the underlying representation.
The same distinction applies to recovery. An error object can say that a digit was invalid. It cannot decide whether the application should ask for another input, reject a message or substitute a default. Those choices depend on the operation the caller is performing. Keeping them outside the parser can make the parser reusable without hiding the failure policy.
I prefer that separation when a failure belongs to ordinary domain behavior. A result can communicate the reason at the component boundary, and the layer with the relevant context can choose its response. It avoids making a low-level helper decide how an entire operation should terminate.
A result type does not imply noexcept
P0323R12 describes expected as a possible bridge between exception-oriented and nonthrowing code. That is a use case for an adapter, not an automatic transformation of everything the function calls. A function returning expected can still invoke an operation that throws. Its result signature alone does not state that exceptions are impossible.
For an interface designer, this creates a separate documentation obligation. The result type should identify failures reported as values. The exception specification and called operations determine whether other failures can escape through exceptions. Conflating those questions can leave a caller prepared for one failure channel while overlooking another.
An adapter can deliberately catch selected exceptions and convert them into an error alternative. That policy needs a sensible error representation and a decision about which exceptions it recognizes. Catching everything without preserving useful information can make failures harder to interpret. The type enables conversion, while the adapter defines its meaning.
The value and error types can also have their own construction costs and behavior. An error carrying dynamically allocated text has a different resource profile from a small enumeration. The choice should follow the information the caller needs. The existence of a standard wrapper does not remove the need to design its payload types.
Error lifetime becomes ordinary object lifetime
The proposal's model stores one active alternative and gives it ordinary object management. This lets an error result travel beyond the immediate stack frame that produced it. A caller can retain the result for later inspection or pass it to another component, subject to the selected value and error types' operations.
That is useful when reporting and recovery occur at different times. An operation can finish with a represented error instead of immediately unwinding through every caller. The application can then decide where the result belongs. This is a design option, without making delayed handling appropriate for every resource or failure.
Payload ownership still matters. An error containing a non-owning reference to temporary diagnostic data would need a valid lifetime relationship. Wrapping that error in expected does not extend the referent's lifetime. The same reasoning applies to a successful value that borrows storage from another object. A result container owns its active payload according to the payload's own semantics.
This makes the type useful for communicating a contract precisely. The interface can choose an owning error representation where later handling is expected. It can instead use a compact code where another layer supplies context. Both fit the same standard vocabulary, while preserving the distinction between representation and policy.
The error category belongs to the component contract
An error enumeration is useful only if its alternatives correspond to decisions the caller can make. A parser can distinguish invalid syntax from an unsupported value when those outcomes have different meanings to its consumer. It need not expose every internal branch as a separate public alternative. Doing so would couple callers to implementation details that may change without altering the parser's contract.
Conversely, reducing every failure to one generic code can discard information the application needs. An interface designer should identify the recovery decisions first, then choose the distinctions the error type preserves. The standard wrapper permits either representation. It cannot determine which distinctions belong to a particular domain.
That is also a versioning concern for a public library. Consumers may branch on the documented error alternatives, so changing their meaning affects observable behavior. Keeping the vocabulary tied to the component's responsibilities makes that behavior easier to preserve across implementation changes.
Publication does not synchronize every toolchain
C++23 includes language additions as well as library facilities. P0847R7 describes explicit object parameters, which let member functions deduce object properties. That requires frontend support. expected is a library facility whose selected operations also depend on the installed library implementation.
The GCC 14 changes illustrate this incremental delivery, separating C++23 implementation work from experimental facilities for the next revision. A project should name the facilities it needs and the supported compiler-library combinations. The year in the published standard's designation does not imply that all installations acquire those facilities together.
Public headers make this requirement especially visible. Exposing std::expected in a return type requires consumers to understand the header and type. An internal adapter can sometimes contain that dependency within an implementation. The decision should follow the project's supported client environments and its intended error contract.
For a component adopting C++23, the useful change is a common representation for an explicitly fallible result. The function can return its value or a typed reason for failure without inventing another wrapper. Its callers still choose how to inspect that result, and the interface still describes any other failure channels. Those responsibilities are easier to discuss when the value and error alternatives have one standard type.