optional, variant, pair, and tuple
Represent optional values and type-safe alternatives with useful standard-library utilities.
Learning objectives
- Use optional when a value may be absent.
- Use variant for one of several possible types.
- Use pair and tuple to group values.
- Check the active state before accessing a value.
Prerequisites: functions, classes, and standard-library types. Review Functions, Classes and objects, and std::string first. The examples use C++17.
std::optional: maybe a value
std::optional<T> represents either a T value or no value. It is useful for a search that may fail without using a magic number or a null pointer.
#include <optional>
std::optional<int> find_id(bool found) {
if (found) return 42;
return std::nullopt;
}
auto result = find_id(true);
if (result) {
// *result is available
}std::variant: one of several types
std::variant<A, B, ...> stores one active alternative from a fixed set of types. It is a type-safe alternative to a union for many use cases. Use std::get when you know the active type, or std::visit to handle alternatives.
#include <variant>
#include <string>
std::variant<int, std::string> value{42};
if (std::holds_alternative<int>(value)) {
int number = std::get<int>(value);
}Pair and tuple
std::pair groups two values; std::tuple groups a fixed number of values that may have different types. Structured bindings (C++17) can give names to their elements.
std::pair<int, std::string> user{7, "Asha"};
auto [id, name] = user;
std::tuple<int, double, bool> record{3, 9.5, true};Use a named struct instead of a tuple when the fields have meaningful domain names and the data is part of a larger model. Named fields make code easier to read.
Common mistakes
- Accessing an empty optional without checking it.
- Calling std::get for the wrong variant alternative, which throws std::bad_variant_access.
- Using long tuples that hide what each position means.
- Assuming optional and variant are pointers; they are value types that manage their contained object.
Practice
- Return optional
from a search function. - Create a variant that can store int, double, or string and handle each alternative.
- Use structured bindings to unpack a pair.
- Replace a three-field tuple with a struct and compare readability.
Practice solutions
Show worked solutions
- Return
std::optional<int>; return the found ID when present andstd::nulloptwhen absent. The caller checks the optional before dereferencing it. #include <iostream> #include <string> #include <variant> std::variant<int, double, std::string> value{42}; std::visit([](const auto& item) { std::cout << item << '\n'; }, value);std::visitinvokes the visitor with the active alternative.- For
std::pair<int, std::string> user{7, "Asha"};, useauto [id, name] = user;. This requires C++17. - A named
structgives each field a meaningful name and lets the type communicate its purpose. A tuple's meaning depends on remembering which position represents which field.
Check your understanding
What does an empty optional contain?
Answer: No T value; it represents absence, which can be checked with its Boolean state or has_value().
Summary
Use optional for a value that may be absent, variant for one of several types, and pair/tuple for small groups. Prefer named structures when fields need meaningful names.