Skip to content
  • optional, variant & any · C++

optional, variant & any

LearnViaNotes @learnvianotes· 3 min read

C++17 vocabulary types: std::optional<T> for “a value or nothing”, std::variant<A, B, …> for “exactly one of these types”, std::any for “any type at all”.

std::optional

C++
#include <iostream>
#include <map>
#include <optional>
#include <string>

std::optional<int> findAge(const std::map<std::string, int>& db, const std::string& name) {
    auto it = db.find(name);
    if (it == db.end()) return std::nullopt;          // “no value” — no sentinel like -1
    return it->second;
}

int main() {
    std::map<std::string, int> db{{"ada", 36}};
    if (auto age = findAge(db, "ada")) std::cout << "ada " << *age << '\n';     // contextual bool
    auto missing = findAge(db, "bob");
    std::cout << missing.has_value() << ' ' << missing.value_or(-1) << '\n';
    try { std::cout << missing.value(); }                                       // throws if empty
    catch (const std::bad_optional_access&) { std::cout << "bad_optional_access\n"; }

    std::optional<std::string> nick;
    nick = "Addy";
    nick.reset();
    nick.emplace(3, 'z');
    std::cout << *nick << ' ' << nick->size() << '\n';
}
Output
ada 36
0 -1
bad_optional_access
zzz 3

std::variant — a type-safe union

C++
#include <iostream>
#include <string>
#include <variant>
#include <vector>

template <class... Fs> struct overloaded : Fs... { using Fs::operator()...; };   // visitor helper

using Json = std::variant<std::nullptr_t, bool, int, std::string>;

int main() {
    std::vector<Json> values{nullptr, true, 42, std::string("hi")};
    for (const auto& v : values) {
        std::visit(overloaded{
            [](std::nullptr_t)          { std::cout << "null\n"; },
            [](bool b)                  { std::cout << "bool " << b << '\n'; },
            [](int i)                   { std::cout << "int " << i << '\n'; },
            [](const std::string& s)    { std::cout << "string " << s << '\n'; },
        }, v);
    }

    std::variant<int, std::string> x = 5;
    std::cout << x.index() << ' ' << std::holds_alternative<int>(x) << ' ' << std::get<int>(x) << '\n';
    x = "now a string";
    if (auto* s = std::get_if<std::string>(&x)) std::cout << *s << '\n';
    try { std::get<int>(x); } catch (const std::bad_variant_access&) { std::cout << "bad_variant_access\n"; }
}
Output
null
bool 1
int 42
string hi
0 1 5
now a string
bad_variant_access

A variant holds exactly one alternative at a time, knows which (index()), and its size is the largest alternative plus a tag. It's the value-semantic alternative to a class hierarchy when the set of types is closed: no heap, no virtual calls, and std::visit forces you to handle every case.

std::any

C++
#include <any>
#include <iostream>
#include <string>

int main() {
    std::any a = 42;
    std::cout << a.type().name() << ' ' << std::any_cast<int>(a) << '\n';
    a = std::string("text");
    if (auto* s = std::any_cast<std::string>(&a)) std::cout << *s << '\n';   // pointer form: nullptr on mismatch
    try { std::any_cast<double>(a); } catch (const std::bad_any_cast&) { std::cout << "bad_any_cast\n"; }
    a.reset();
    std::cout << a.has_value() << '\n';
}
Output
i 42
text
bad_any_cast
0
TypeHoldsChecked byTypical use
optional<T>A T or nothinghas_value(), if (opt)Maybe-absent results
variant<Ts...>Exactly one of a closed set of typesindex(), holds_alternative, visitState machines, AST nodes, message types, parse results
anyAny copyable typeany_cast (run time)Rarely: plugin/config bags with unknown types

When would you use std::variant instead of inheritance?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report