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
#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';
}ada 36
0 -1
bad_optional_access
zzz 3std::variant — a type-safe union
#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"; }
}null
bool 1
int 42
string hi
0 1 5
now a string
bad_variant_accessA 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
#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';
}i 42
text
bad_any_cast
0| Type | Holds | Checked by | Typical use |
|---|---|---|---|
optional<T> | A T or nothing | has_value(), if (opt) | Maybe-absent results |
variant<Ts...> | Exactly one of a closed set of types | index(), holds_alternative, visit | State machines, AST nodes, message types, parse results |
any | Any copyable type | any_cast (run time) | Rarely: plugin/config bags with unknown types |
When would you use std::variant instead of inheritance?