C++ PROGRAMMING ยท 14 MIN STUDY

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

  1. Return optional from a search function.
  2. Create a variant that can store int, double, or string and handle each alternative.
  3. Use structured bindings to unpack a pair.
  4. Replace a three-field tuple with a struct and compare readability.

Practice solutions

Show worked solutions
  1. Return std::optional<int>; return the found ID when present and std::nullopt when absent. The caller checks the optional before dereferencing it.
  2. #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::visit invokes the visitor with the active alternative.
  3. For std::pair<int, std::string> user{7, "Asha"};, use auto [id, name] = user;. This requires C++17.
  4. A named struct gives 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.

References