C++23 library features: expected and print
Explore selected C++23 library improvements, especially std::expected and std::print, while checking compiler and standard-library support.
Prerequisites and outcomes
Prerequisites: C++17, templates, strings, and basic error handling. Review Function and class templates, std::string, Exceptions, and optional and variant first. After this lesson, you can explain the purpose of std::expected and std::print, read a simple example, and verify whether your toolchain implements a feature before using it.
Standards versus compiler support
C++23 is a language and library standard, but compiler front ends and standard-library implementations adopt features at different times. A compiler accepting -std=c++23 does not guarantee every C++23 library facility is implemented. Check the compiler and library release notes and feature-test macros.
std::expected: a value or an error
std::expected<T, E> represents either a successful value of type T or an error value of type E. It is useful when failure is an expected outcome that callers should handle explicitly, rather than an exceptional programming failure.
#include <expected>
#include <string>
#include <charconv>
#include <system_error>
std::expected<int, std::string> parse_id(const std::string& text) {
int value{};
auto r = std::from_chars(text.data(), text.data() + text.size(), value);
if (r.ec != std::errc{} || r.ptr != text.data() + text.size())
return std::unexpected("ID must be a whole integer");
if (value <= 0) return std::unexpected("ID must be positive");
return value;
}A caller can inspect the result with has_value() or use the explicit Boolean conversion, then access value() or error(). Calling value() when no value exists throws std::bad_expected_access<E> for the error type E; check first or use a deliberate fallback such as value_or when appropriate.
Handle both outcomes
#include <iostream>
auto result = parse_id("42");
if (result) {
std::cout << *result;
} else {
std::cerr << result.error();
}This makes the error part of the function's return type. It does not automatically force callers to handle the error; good API design and tests are still necessary.
std::print: formatted output
#include <print>
int main() {
int score = 95;
std::print("Score: {}\n", score);
}std::print provides formatted output using format-string replacement fields. It is standardized in C++23, but support depends on the standard library version. Do not assume it is available merely because another C++23 feature compiles.
Feature-test macros
#include <version>
#if defined(__cpp_lib_expected) && __cpp_lib_expected >= 202202L
// std::expected is available in this standard library
#endif
#if defined(__cpp_lib_print) && __cpp_lib_print >= 202207L
// std::print is available in this standard library
#endifFeature-test macros provide a programmatic check for library features. They are not a replacement for compiling a small test with the exact compiler and standard-library combination used by your project.
Choose the right error mechanism
- Use a return value such as
expectedwhen failure is a normal, anticipated result callers should inspect. - Use exceptions when an operation cannot fulfill its contract and the calling layer is designed to handle exceptional failure.
- Use assertions for programmer assumptions, not for untrusted user input.
Common mistakes
- Assuming all C++23 library features ship together across compilers.
- Calling
value()without checking whether an expected object contains a value. - Using
expectedeverywhere without considering whether a simpler return type or exception is clearer. - Checking only the compiler version instead of the standard-library feature macro and actual build.
Practice
- What are the two alternatives represented by
expected<T, E>? - Why might
-std=c++23still fail to compile astd::printexample? - What should you check before calling
value()?
Show reasoned answers
- A value of type T or an error of type E.
- The compiler's standard library may not yet implement that C++23 library facility.
- Check
has_value()or the object's Boolean state, or use another deliberate access strategy.
Summary and next step
C++23 adds useful library facilities, but support is implementation-dependent during adoption. Treat standard version, compiler version, and standard-library support as separate checks. Use expected to make anticipated errors explicit and print for concise formatted output when supported.