Search Knowledge

© 2026 LIBREUNI PROJECT

Modern C++ Programming / Modern C++ Features

std::optional and std::variant

std::optional: Representing Optional Values

C++17’s std::optional<T> represents a value that may or may not be present, avoiding special sentinel values or pointers.

Basic Usage

#include <optional>

std::optional<int> parse_int(const std::string& s) {
    try {
        return std::stoi(s);
    } catch (...) {
        return std::nullopt;  // No value
    }
}

auto result = parse_int("42");
if (result) {  // or result.has_value()
    std::cout << *result << '\\n';  // Dereference to get value
} else {
    std::cout << "Parse failed\\n";
}

Accessing Values

std::optional<int> opt = 42;

// Safe access with default
int value = opt.value_or(0);  // Returns 42, or 0 if empty

// Direct access (throws if empty)
int v1 = opt.value();  // Throws std::bad_optional_access if empty

// Unsafe access (UB if empty)
int v2 = *opt;  // Like pointer dereference

// Check before access
if (opt.has_value()) {
    std::cout << opt.value();
}

Creating Optionals

std::optional<int> empty;  // No value
std::optional<int> zero{0};  // Contains 0
std::optional<int> forty_two = 42;
std::optional<int> none = std::nullopt;

// In-place construction
std::optional<std::string> str{std::in_place, 10, 'x'};  // "xxxxxxxxxx"

Modifying Optionals

std::optional<int> opt;

opt = 42;  // Assign value
opt.reset();  // Clear value (now empty)
opt.emplace(100);  // Construct value in-place

Monadic Operations (C++23)

std::optional<int> opt = 42;

// and_then: Apply function returning optional
auto result = opt.and_then([](int x) -> std::optional<int> {
    return x > 0 ? std::optional{x * 2} : std::nullopt;
});

// transform: Apply function returning value
auto doubled = opt.transform([](int x) { return x * 2; });

// or_else: Provide alternative
auto val = std::optional<int>{}.or_else([]{ return std::optional{42}; });

Use Cases

class Config {
    std::optional<std::string> username_;
    std::optional<int> port_;
    
public:
    void set_username(const std::string& name) { username_ = name; }
    std::optional<std::string> get_username() const { return username_; }
    
    int get_port() const { return port_.value_or(8080); }  // Default port
};

std::variant: Type-Safe Unions

std::variant is a type-safe union that can hold one of several types at any time.

Basic Usage

#include <variant>

std::variant<int, double, std::string> value;

value = 42;  // Holds int
value = 3.14;  // Now holds double
value = "hello";  // Now holds string

// Access by index
int i = std::get<0>(value);  // Throws if not int

// Access by type (must be unique)
std::string s = std::get<std::string>(value);

// Safe access
if (auto ptr = std::get_if<std::string>(&value)) {
    std::cout << *ptr;
}

Visiting Variants

std::variant<int, double, std::string> v = 42;

std::visit([](auto&& arg) {
    using T = std::decay_t<decltype(arg)>;
    if constexpr (std::is_same_v<T, int>) {
        std::cout << "int: " << arg << '\\n';
    } else if constexpr (std::is_same_v<T, double>) {
        std::cout << "double: " << arg << '\\n';
    } else {
        std::cout << "string: " << arg << '\\n';
    }
}, v);

Overloaded Pattern

Helper for multiple lambdas:

template<class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };

template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;  // Deduction guide

std::variant<int, double, std::string> v = "hello";

std::visit(overloaded{
    [](int x) { std::cout << "int: " << x; },
    [](double x) { std::cout << "double: " << x; },
    [](const std::string& x) { std::cout << "string: " << x; }
}, v);

Variant State

std::variant<int, double> v = 3.14;

std::cout << v.index();  // 1 (double is second alternative)

if (std::holds_alternative<double>(v)) {
    std::cout << "Holds double\\n";
}

Error Handling with Variant

Alternative to exceptions:

struct Error {
    std::string message;
};

std::variant<int, Error> divide(int a, int b) {
    if (b == 0) {
        return Error{"Division by zero"};
    }
    return a / b;
}

auto result = divide(10, 2);

std::visit(overloaded{
    [](int value) { std::cout << "Result: " << value; },
    [](const Error& e) { std::cerr << "Error: " << e.message; }
}, result);

variant vs optional vs union

Featureoptionalvariantunion
Type SafetyYesYesNo
Knows TypeYesYesNo
Empty StateYesNo*N/A
Non-Trivial TypesYesYesNo

*variant has std::monostate for empty state

std::variant<std::monostate, int, double> v;  // Default: monostate (empty)
Conceptual Check

What's the key advantage of std::optional over using nullptr or -1 as sentinel values?

Runtime Environment

Interactive Lab

1#include <iostream>
2#include <optional>
3#include <variant>
4#include <string>
5 
6std::optional<int> safe_divide(int a, int b) {
7 if (b == 0) return std::nullopt;
8 return a / b;
9}
10 
11int main() {
12 auto r1 = safe_divide(10, 2);
13 std::cout << "10/2 = " << r1.value_or(-1) << '\n';
14
15 auto r2 = safe_divide(10, 0);
16 std::cout << "10/0 = " << r2.value_or(-1) << '\n';
17
18 std::variant<int, double, std::string> v = 42;
19 std::cout << "Variant holds index " << v.index() << '\n';
20
21 v = "hello";
22 std::visit([](auto&& arg) {
23 std::cout << "Value: " << arg << '\n';
24 }, v);
25
26 return 0;
27}
System Console

Waiting for signal...