C++ PROGRAMMING ยท 13 MIN STUDY

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
#endif

Feature-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 expected when 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 expected everywhere 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

  1. What are the two alternatives represented by expected<T, E>?
  2. Why might -std=c++23 still fail to compile a std::print example?
  3. What should you check before calling value()?
Show reasoned answers
  1. A value of type T or an error of type E.
  2. The compiler's standard library may not yet implement that C++23 library facility.
  3. 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.

References