optional
Introduced in C++17, std::optional represents a state where a value "may or may not exist" in a type-safe way. It replaces traditional patterns like "returning -1 on failure" or "returning nullptr", making intent clear. Also from C++17, std::variant is a type-safe union that holds exactly one of several listed types and can be used with std::visit for pattern-matching style processing.
Syntax
#include <optional>
#include <string>
// optional variable declaration (no initial value = no value state)
std::optional<std::string> name; // No value (equivalent to std::nullopt)
std::optional<int> score = 42; // Has a value
// Assign no-value state
name = std::nullopt;
// Check and retrieve the value
if (name.has_value()) {
std::cout << name.value() << std::endl;
}
// Can also check with operator bool
if (score) {
std::cout << *score << std::endl; // Retrieve with *
}
// Returns a default value if no value is present
std::string displayName = name.value_or("unnamed");
// ========================================
// std::variant
// ========================================
#include <variant>
// variant that holds either int or std::string
std::variant<int, std::string> data = 100;
data = std::string("Unit-01");
// Check the current type
if (std::holds_alternative<std::string>(data)) {
std::cout << std::get<std::string>(data) << std::endl;
}
// Process with std::visit
std::visit([](auto&& val) {
std::cout << val << std::endl;
}, data);
Member / Function Reference
| Syntax / Function | Description |
|---|---|
| std::optional<T> | Wrapper type that holds a value of type T, or a no-value state (std::nullopt). |
| has_value() | Returns bool indicating whether a value is held. |
| value() | Returns the held value. Throws std::bad_optional_access if no value is present. |
| value_or(default) | Returns the held value if present, otherwise returns the default argument. |
| operator bool / operator* | Use if (opt) to check for a value, and *opt to retrieve it. |
| reset() | Destroys the held value and enters the no-value state. |
| std::variant<T1, T2, ...> | Type-safe union that always holds exactly one of the listed types. |
| std::holds_alternative<T>(v) | Returns bool indicating whether variant v currently holds type T. |
| std::get<T>(v) | Retrieves the value of type T from variant v. Throws std::bad_variant_access if the type is different. |
| std::get<N>(v) | Retrieves the value as the type at index N. |
| std::visit(visitor, v) | Automatically calls the overload corresponding to the current type of the variant. |
| v.index() | Returns the index (0-based) of the type currently held in the variant. |
Sample Code
eva_optional.cpp
#include <iostream>
#include <optional>
#include <string>
#include <vector>
// Returns the last mission for a pilot
// Returns std::nullopt if no sortie record exists
std::optional<std::string> getLastMission(const std::string& pilotName) {
if (pilotName == "Ikari Shinji") {
return "18th Angel: Kaworu battle (Unit-02 support)";
} else if (pilotName == "Ayanami Rei") {
return "16th Angel: Armisael battle (Unit-00 sortie)";
} else if (pilotName == "Katsuragi Misato") {
return "Strategic command for the 17th Angel";
}
return std::nullopt;
}
void displayMission(const std::string& pilotName) {
std::optional<std::string> mission = getLastMission(pilotName);
if (mission.has_value()) {
std::cout << pilotName << "'s last sortie: " << mission.value() << std::endl;
} else {
std::cout << pilotName << " has no sortie record." << std::endl;
}
std::string display = mission.value_or("(no record)");
std::cout << " Summary: " << display << std::endl;
}
int main() {
std::cout << "=== Pilot Sortie Records ===" << std::endl << std::endl;
std::vector<std::string> pilots = {
"Ikari Shinji",
"Ayanami Rei",
"Katsuragi Misato",
"Nagisa Kaworu", // No sortie record
"Asuka Langley" // No sortie record
};
for (int i = 0; i < (int)pilots.size(); i++) {
displayMission(pilots[i]);
std::cout << std::endl;
}
// Using operator bool and operator* shorthand
std::cout << "=== Shorthand ===" << std::endl;
std::optional<int> syncRate = 141;
if (syncRate) {
std::cout << "Shinji's sync rate: " << *syncRate << "%" << std::endl;
}
syncRate.reset();
std::cout << "After sync graph loss: "
<< syncRate.value_or(0) << "%" << std::endl;
return 0;
}
g++ -std=c++17 eva_optional.cpp -o eva_optional && ./eva_optional === Pilot Sortie Records === Ikari Shinji's last sortie: 18th Angel: Kaworu battle (Unit-02 support) Summary: 18th Angel: Kaworu battle (Unit-02 support) Ayanami Rei's last sortie: 16th Angel: Armisael battle (Unit-00 sortie) Summary: 16th Angel: Armisael battle (Unit-00 sortie) Katsuragi Misato's last sortie: Strategic command for the 17th Angel Summary: Strategic command for the 17th Angel Nagisa Kaworu has no sortie record. Summary: (no record) Asuka Langley has no sortie record. Summary: (no record) === Shorthand === Shinji's sync rate: 141% After sync graph loss: 0%
eva_variant.cpp
#include <iostream>
#include <variant>
#include <string>
#include <vector>
using EvaData = std::variant<int, std::string, double>;
struct EvaDataPrinter {
void operator()(int val) const {
std::cout << "[Code] " << val << std::endl;
}
void operator()(const std::string& val) const {
std::cout << "[Name] " << val << std::endl;
}
void operator()(double val) const {
std::cout << "[Output] " << val << " MW" << std::endl;
}
};
int main() {
std::cout << "=== Eva Spec Data (variant) ===" << std::endl << std::endl;
std::vector<EvaData> specs = {
0,
std::string("Ayanami Rei"),
17500.0,
1,
std::string("Ikari Shinji"),
28000.0,
};
for (int i = 0; i < (int)specs.size(); i++) {
std::visit(EvaDataPrinter{}, specs[i]);
}
std::cout << std::endl;
std::cout << "=== Type Check and Value Retrieval ===" << std::endl;
EvaData pilotData = std::string("Nagisa Kaworu");
if (std::holds_alternative<std::string>(pilotData)) {
std::cout << "Pilot: " << std::get<std::string>(pilotData) << std::endl;
}
pilotData = 5;
std::cout << "Index: " << pilotData.index() << std::endl;
std::cout << "Code: " << std::get<int>(pilotData) << std::endl;
std::cout << std::endl << "=== Catching bad_variant_access ===" << std::endl;
try {
std::string s = std::get<std::string>(pilotData); // pilotData is int, not string
} catch (const std::bad_variant_access& e) {
std::cout << "Exception caught: " << e.what() << std::endl;
}
return 0;
}
g++ -std=c++17 eva_variant.cpp -o eva_variant && ./eva_variant === Eva Spec Data (variant) === [Code] 0 [Name] Ayanami Rei [Output] 17500 MW [Code] 1 [Name] Ikari Shinji [Output] 28000 MW === Type Check and Value Retrieval === Pilot: Nagisa Kaworu Index: 0 Code: 5 === Catching bad_variant_access === Exception caught: std::bad_variant_access
Common mistake 1: Calling value() without checking for a value
Calling value() on a std::optional that holds no value throws std::bad_optional_access. Always check for a value before calling it.
// NG: Calling value() without checking throws an exception std::optional<std::string> mission = std::nullopt; std::string m = mission.value(); // Throws bad_optional_access
OK: Check with has_value() first, or use value_or().
// OK: Check with has_value() before calling value()
if (mission.has_value()) {
std::cout << mission.value() << std::endl;
}
// OK: Use value_or() to specify a default value
std::string display = mission.value_or("(no record)");
Overview
std::optional<T> is a C++17 type representing "a value that may or may not exist." Using it as a return value replaces conventional patterns like -1, nullptr, or empty strings to represent errors or unset values. Check for a value with has_value() or operator bool; retrieve it with value(), operator*, or value_or(). Calling value() with no value present throws std::bad_optional_access, so always check first with has_value() or use value_or(). std::variant<T1, T2, ...> is a type-safe union that always holds exactly one of the listed types. Use std::holds_alternative<T> to check the type and std::get<T> to retrieve the value. Passing a visitor (an object with operator() overloads for each type) to std::visit automatically calls the overload for the current type, ensuring no type case is missed. Both std::optional and std::variant require C++17 or later with the -std=c++17 option. See also: Exception Handling / auto and Type Deduction / tuple / pair.
If you find any errors or copyright issues, please contact us.