Exploring std::any
and std::variant
in Modern C++
C++ | Tips
With the evolution of C++, the introduction of <any>
and <variant>
in C++17 has provided powerful tools for developers to handle flexible type storage and type-safe alternatives. In this post, we'll dive into these two utilities, explore their differences, and understand how to effectively use them.
What is <any>
?
<any>
is part of the Standard Template Library (STL) and is designed for storing values of any type. It enables runtime type storage and retrieval, making it a highly flexible tool for scenarios where the type is not known at compile-time.
Key Features of <any>
:
- Can store values of any type.
- Requires runtime type checks to retrieve the stored value.
- Flexible but less performant than type-safe alternatives like
<variant>
.
How to Use <any>
:
#include <iostream>
#include <any>
int main() {
std::any value;
// Store an integer
value = 2;
std::cout << "Stored integer: " << std::any_cast<int>(value) << "\n";
// Store a string
value = std::string("Hello, World!");
std::cout << "Stored string: " << std::any_cast<std::string>(value) << "\n";
return 0;
}
Stored integer: 2
Stored string: Hello, World!
In the above example, std::any
holds both an integer and a string at different times. We use std::any_cast
to safely retrieve the stored value. If you attempt to retrieve a value of the wrong type, std::bad_any_cast
will be thrown.
When to Use <any>
:
- When you need to store values of arbitrary types.
- When type safety is not a strict requirement.
What is <variant>
?
<variant>
is a type-safe alternative to unions introduced in C++17. Unlike <any>
, <variant>
can only hold one value at a time, but it must be one of a predefined set of types.
Key Features of <variant>
:
- Type-safe at compile time.
- No runtime overhead for type checking.
- Supports pattern matching using
std::visit
for clean handling of different types.
How to Use <variant>
:
#include <iostream>
#include <variant>
int main() {
std::variant<int, std::string> var;
// Assign an integer
var = 10;
std::cout << "Integer value in variant: " << std::get<int>(var) << "\n";
// Assign a string
var = "Hello, Variant!";
std::cout << "String value in variant: " << std::get<std::string>(var) << "\n";
return 0;
}
Integer value in variant: 10
String value in variant: Hello, Variant!
Pattern Matching with <variant>
:
One of the most powerful features of <variant>
is pattern matching using std::visit
. This allows you to handle each possible type cleanly:
#include <iostream>
#include <variant>
int main() {
std::variant<int, std::string> var = "Pattern Matching";
std::visit([](auto&& arg) {
std::cout << "Value: " << arg << "\n";
}, var);
return 0;
}
Here, std::visit
automatically deduces the type currently stored in the variant
and executes the appropriate logic.
When to Use <variant>
:
- When you need to store one value out of a predefined set of types.
- When you need type safety and want to avoid runtime errors.
Key Differences Between std::any
and std::variant
Practical Use Cases
When to Choose <any>
:
- Logging frameworks that need to handle arbitrary types.
- General-purpose containers that store heterogeneous data.
When to Choose <variant>
:
- Modeling state transitions in finite state machines.
- Handling tagged unions in type-safe ways.
Conclusion
Both <any>
and <variant>
are powerful tools introduced in C++17, each with its own strengths. Use <any>
for flexibility when the types are not known at compile-time. Use <variant>
for compile-time type safety and better performance. By understanding their differences and use cases, you can write more robust and maintainable C++ code.