Expert · Modern C++: lambdas, C++17 and C++20 essentials · topic 32 of 42 · 30 min
std::optional, std::variant and std::string_view
Explanation
The problem with magic values
Old code often returns -1 for "not found", an empty string for "no name", or a null pointer for "no object". Readers must remember each convention, and the caller can forget to check. C++17 added "vocabulary types" that say what you mean directly in the type.
std::optional: a value or nothing
#include <iostream>
#include <optional>
#include <string>
#include <vector>
std::optional<int> findRoll(const std::vector<std::string>& names, const std::string& who) {
for (std::size_t i = 0; i < names.size(); ++i) {
if (names[i] == who) return static_cast<int>(i) + 1;
}
return std::nullopt; // "no value"
}
int main() {
std::vector<std::string> cls = {"Asha", "Ravi", "Meena"};
if (auto roll = findRoll(cls, "Ravi")) { // true if it has a value
std::cout << "Roll " << *roll << '\n'; // Roll 2
}
auto missing = findRoll(cls, "Om");
std::cout << missing.value_or(-1) << '\n'; // -1
std::cout << missing.has_value() << '\n'; // 0
}std::variant: one of several types
#include <iostream>
#include <string>
#include <variant>
using PaymentResult = std::variant<std::string, int>; // a transaction id OR an error code
PaymentResult pay(int amount) {
if (amount <= 0) return 400; // error code
return std::string("TXN") + std::to_string(amount); // transaction id
}
int main() {
auto r = pay(500);
if (std::holds_alternative<std::string>(r)) {
std::cout << "Paid: " << std::get<std::string>(r) << '\n'; // Paid: TXN500
}
std::visit([](const auto& v) { std::cout << "Result: " << v << '\n'; }, pay(0)); // Result: 400
}variant is a type-safe replacement for C unions. It is great for modelling "this OR that": a JSON value (number, string, bool, list), a parse result, or a shape that is a circle or a rectangle, without needing inheritance and heap allocation.
std::string_view: look, do not own
#include <string_view>
bool isUpiId(std::string_view s) { // accepts std::string, literals, substrings
auto at = s.find('@');
return at != std::string_view::npos && at > 0 && at + 1 < s.size();
}
std::string id = "asha@okbank";
isUpiId(id); // no copy
isUpiId("ravi@upi"); // no std::string created
std::string_view domain = std::string_view(id).substr(5); // "okbank", no allocationWhen to use which
| Situation | Type |
|---|---|
| A function may have no result | std::optional<T> |
| A value can be one of a fixed set of types | std::variant<A, B, ...> |
| A function only reads text it does not keep | std::string_view parameter |
| You need to store or own text | std::string |
| Any type at all, decided at run time (rare) | std::any |
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
std::optional, std::variant and std::string_view · 1/6
C plus plus seventeen added types that say exactly what you mean: optional, variant and string view.
Prefer a video by a teacher? Find free videos on “c++17 std::optional std::variant std::string_view tutorial” on YouTube ↗
In real life: Parsing a PIN code from a form
A delivery app reads the PIN code typed in an address form. Instead of returning 0 or -1 on bad input, the parser returns std::optional<int>; the caller cannot forget that "invalid" is possible. It takes a std::string_view so it can parse part of a longer address line without copying.
#include <charconv>
#include <iostream>
#include <optional>
#include <string_view>
std::optional<int> parsePin(std::string_view s) {
if (s.size() != 6 || s[0] == '0') return std::nullopt;
int value = 0;
auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value);
if (ec != std::errc() || ptr != s.data() + s.size()) return std::nullopt;
return value;
}
int main() {
std::cout << parsePin("411001").value_or(-1) << '\n'; // 411001
std::cout << parsePin("41A001").value_or(-1) << '\n'; // -1
std::cout << parsePin("12345").has_value() << '\n'; // 0
}Practice (0/4 done)
std::optional<int> x; What is x.has_value()?
What does this print? std::optional<int> a = 5, b; std::cout << a.value_or(0) + b.value_or(10);
Which line creates a dangling string_view?
Write std::optional<double> safeDivide(double a, double b) that returns nothing when b is 0. Then define using Shape = std::variant<Circle, Rect>; (two small structs) and a function double area(const Shape&) that uses std::visit. Print the area of one circle and one rectangle.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- C++20 ranges, views and other C++20 essentialsModern C++: lambdas, C++17 and C++20 essentials · same module
- Lambdas: small functions written in placeModern C++: lambdas, C++17 and C++20 essentials · same module
- Enums and OptionRust Programming · Structs, enums and pattern matching
- Your first program: main, cout and namespacesFirst steps: compiling, output, variables and input
- Reading input and formatting outputFirst steps: compiling, output, variables and input
- Smart pointers 1: std::unique_ptrTemplates, smart pointers and move semantics
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What is std::nullopt?
Answer: The value that means "this optional is empty"
Returning std::nullopt from a function returning std::optional<T> gives an empty optional.
What happens with std::get<int>(v) when the variant v holds a std::string?
Answer: Throws std::bad_variant_access
std::get checks the active alternative and throws on a mismatch.
Why is std::string_view a good parameter type for read-only text?
Answer: It accepts std::string, literals and substrings without copying
A view is just a pointer and a length.
Which C++ standard introduced optional, variant and string_view?
Answer: C++17
All three came in C++17.
std::string a = "apple", b = "Apple"; Is a == b?Related
Answer: No, comparison is case sensitive
Characters are compared exactly; 'a' and 'A' are different.
What is std::span<const int> good for?Related
Answer: Passing any contiguous array (vector, std::array, C array) to a function without copying
span is a non-owning view of contiguous elements.
What is the main advantage of std::vector over a C-style array?Related
Answer: Its size can change at run time and it knows its size
vector grows with push_back and reports size(); it also manages its own memory.
What can an enum variant hold in Rust?Related
Answer: Any data: none, a tuple of values, or named fields
Rust variants can be unit-like, tuple-like or struct-like.
From Enums and Option
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 1 more topic in “Modern C++: lambdas, C++17 and C++20 essentials” before its exam: C++20 ranges, views and other C++20 essentials.
- Module 8 exam: Modern C++ · 12 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Exceptions, concurrency and building projects with CMake. 42 topics left in the course before the final exam.
← Previous: auto, structured bindings and other C++17 conveniences