Library`std::optional`, `std::variant`, `std::any`

`std::optional`, `std::variant`, `std::any`

Learn about `std::optional`, `std::variant`, `std::any` as part of C++ Modern Systems Programming and Performance

C++ Type-Safe Data Handling: std::optional, std::variant, std::any

Modern C++ offers powerful tools to manage data that might be absent, can hold one of several types, or can hold any type. These features enhance type safety, reduce boilerplate code, and improve program robustness. This module explores

code
std::optional
,
code
std::variant
, and
code
std::any
.

std::optional: Representing Absence of a Value

code
std::optional
is a template class that represents a value of type
code
T
which may or may not be present. It's a safer alternative to using null pointers or special sentinel values to indicate the absence of data.

`std::optional` elegantly handles values that might not exist.

Think of std::optional as a box that might contain a value, or it might be empty. This avoids the ambiguity and potential errors associated with null pointers.

When a function might fail to produce a result or a variable might not have an assigned value, std::optional<T> can be used. It has two states: engaged (containing a value) and disengaged (empty). You can check if it contains a value using has_value() or by converting it to a boolean. Accessing the value is done via value() (which throws if disengaged) or operator* and operator-> (which have undefined behavior if disengaged). std::nullopt is used to represent the disengaged state.

What is the primary purpose of std::optional<T>?

To represent a value that may or may not be present, providing a type-safe way to handle optional values.

std::variant: Type-Safe Union

code
std::variant
is a template class that represents a type-safe union. It can hold a value of any of the types specified in its template parameter list, but only one at a time.

`std::variant` allows a variable to hold one of several distinct types.

Imagine a versatile container that can hold either an integer, a string, or a double, but never more than one of these at any given moment. This is what std::variant provides.

Unlike a C-style union, std::variant knows which type it currently holds. You can assign a value of any of its alternative types. To access the value, you typically use std::get<Type>(variant) or std::get<index>(variant), which will throw std::bad_variant_access if the variant does not currently hold that type. A safer approach is to use std::visit, which applies a callable object (like a lambda or function object) to the currently held value, handling all possible types.

Visualizing std::variant: Imagine a single slot that can dynamically hold different types of data. When you assign an int, the slot contains an integer. When you assign a std::string, the slot now contains a string, and the previous integer is gone. std::visit allows you to perform an action based on the current type held within the variant, ensuring you only operate on valid data.

📚

Text-based content

Library pages focus on text content

What is the main advantage of std::variant over a C-style union?

std::variant is type-safe and knows which type it currently holds, preventing common errors associated with C-style unions.

std::any: Type-Erasure for Any Type

code
std::any
is a type-safe container for single values of any copy-constructible type. It uses type erasure to store values of different types in a uniform way.

`std::any` allows storing values of completely unrelated types.

Think of std::any as a universal holder. You can put an integer in it, then later replace it with a floating-point number, or even a custom object, without needing to know the specific types beforehand.

When you store a value in std::any, its type information is preserved. To retrieve the value, you must specify the expected type using std::any_cast<T>(any_object). If the std::any object does not contain a value of type T, std::any_cast will throw std::bad_any_cast. This makes std::any useful for scenarios where you need to handle heterogeneous data, such as in configuration systems, plugin architectures, or generic data structures, but it should be used judiciously due to potential runtime type checking overhead and exceptions.

While std::any offers flexibility, it sacrifices compile-time type checking for the stored value. Prefer std::variant when the set of possible types is known and limited.

When might std::any be a more suitable choice than std::variant?

When the set of possible types is not known at compile time, or when dealing with a very large or dynamic set of unrelated types.

Comparison: std::optional, std::variant, std::any

Featurestd::optional<T>std::variant<Types...>std::any
PurposeRepresents a value that may or may not be presentHolds one of a fixed set of typesHolds a value of any copy-constructible type
Type KnowledgeKnows the single possible type TKnows the finite set of possible typesType information is erased at compile time
Accesshas_value(), value(), *, ->std::get<Type>, std::get<index>, std::visitstd::any_cast<T>
Error HandlingThrows std::bad_optional_access (for value())Throws std::bad_variant_accessThrows std::bad_any_cast
Use CaseOptional function return values, nullable membersState machines, discriminated unions, message passingHeterogeneous collections, dynamic data storage

Learning Resources

cppreference.com: std::optional(documentation)

The official documentation for `std::optional`, detailing its members, usage, and examples.

cppreference.com: std::variant(documentation)

Comprehensive documentation for `std::variant`, including `std::visit` and common use cases.

cppreference.com: std::any(documentation)

Detailed information on `std::any`, its type erasure mechanism, and how to use `std::any_cast`.

Modern C++: std::variant Explained(blog)

A clear and concise explanation of `std::variant` with practical code examples.

Effective Modern C++: std::optional(blog)

A summary of Scott Meyers' advice on using `std::optional` effectively in C++.

Understanding std::any in C++17(blog)

An in-depth look at `std::any`, its purpose, and how it differs from other type-handling mechanisms.

C++ Core Guidelines: std::optional(documentation)

Best practices and guidelines for using `std::optional` from the C++ Core Guidelines.

C++ Core Guidelines: std::variant(documentation)

Recommendations for using `std::variant` according to the C++ Core Guidelines.

C++ Core Guidelines: std::any(documentation)

Guidance on when and how to use `std::any` safely and effectively.

C++ Type Erasure with std::any(video)

A video tutorial explaining the concept of type erasure and how `std::any` implements it in C++.