std::variant
is a type-safe union available in C++17 and later. It allows you to store a value that could be one of several different types, but only one type at a time. Unlike traditional unions in C++, std::variant
provides type safety by ensuring that only one type is active at any given time and by providing ways to query and retrieve the currently active type.
Let's say you want to store either an int
or a std::string
in a single variable. Using std::variant
, you can define such a variable and safely use it.
Here’s a simple example:
#include <iostream>
#include <variant>
#include <string>
int main() {
// Define a variant that can hold either an int or a std::string
std::variant<int, std::string> myVariant;
// Assign an int to the variant
myVariant = 42;
// Check the type and print the value
if (std::holds_alternative<int>(myVariant)) {
std::cout << "Integer: " << std::get<int>(myVariant) << std::endl;
}
// Assign a string to the variant
myVariant = std::string("Hello, Variant!");
// Check the type and print the value
if (std::holds_alternative<std::string>(myVariant)) {
std::cout << "String: " << std::get<std::string>(myVariant) << std::endl;
}
return 0;
}
-
Declaration:
std::variant<int, std::string> myVariant;
This declares a variable
myVariant
that can hold either anint
or astd::string
. -
Assigning an
int
:myVariant = 42;
Here, the variant is assigned an integer value
42
. -
Checking the type and getting the value:
if (std::holds_alternative<int>(myVariant)) { std::cout << "Integer: " << std::get<int>(myVariant) << std::endl; }
This checks if the variant currently holds an
int
usingstd::holds_alternative<int>
. If true, it retrieves the value usingstd::get<int>
. -
Assigning a
std::string
:myVariant = std::string("Hello, Variant!");
The variant is now assigned a string value.
-
Checking the type and getting the value (string):
if (std::holds_alternative<std::string>(myVariant)) { std::cout << "String: " << std::get<std::string>(myVariant) << std::endl; }
Similarly, this checks if the variant holds a
std::string
and retrieves the value.
Consider a configuration system where values can be of different types, such as integers, floating-point numbers, strings, or boolean values. Using std::variant
allows you to store these different types in a single container.
#include <iostream>
#include <variant>
#include <string>
#include <unordered_map>
using ConfigValue = std::variant<int, double, std::string, bool>;
void printConfigValue(const ConfigValue& value) {
std::visit([](auto&& arg) { std::cout << arg << std::endl; }, value);
}
int main() {
std::unordered_map<std::string, ConfigValue> config;
config["max_connections"] = 100;
config["timeout"] = 30.5;
config["username"] = std::string("admin");
config["debug_mode"] = true;
for (const auto& [key, value] : config) {
std::cout << key << ": ";
printConfigValue(value);
}
return 0;
}
-
Using
std::variant
for Configuration Values:- The
ConfigValue
type is defined as astd::variant
that can hold anint
,double
,std::string
, orbool
. - The configuration values are stored in a
std::unordered_map
where each key maps to aConfigValue
.
- The
-
Printing Configuration Values:
- The
printConfigValue
function usesstd::visit
to handle each possible type in the variant and print its value.
- The
In a graphics or CAD application, you might have different types of shapes (e.g., circles, rectangles, triangles) but want to store them in a single container for processing.
#include <iostream>
#include <variant>
#include <vector>
struct Circle {
double radius;
};
struct Rectangle {
double width, height;
};
struct Triangle {
double base, height;
};
using Shape = std::variant<Circle, Rectangle, Triangle>;
void printShape(const Shape& shape) {
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Circle>) {
std::cout << "Circle with radius: " << arg.radius << std::endl;
} else if constexpr (std::is_same_v<T, Rectangle>) {
std::cout << "Rectangle with width: " << arg.width << " and height: " << arg.height << std::endl;
} else if constexpr (std::is_same_v<T, Triangle>) {
std::cout << "Triangle with base: " << arg.base << " and height: " << arg.height << std::endl;
}
}, shape);
}
int main() {
std::vector<Shape> shapes = {
Circle{5.0},
Rectangle{3.0, 4.0},
Triangle{6.0, 2.5}
};
for (const auto& shape : shapes) {
printShape(shape);
}
return 0;
}
-
Using
std::variant
for Shapes:- The
Shape
type is defined as astd::variant
that can hold aCircle
,Rectangle
, orTriangle
.
- The
-
Printing Shape Information:
- The
printShape
function usesstd::visit
with a lambda to check the active type in the variant and print the corresponding shape information.
- The
- Type Safety: Ensures that only one type is active and that the operations are valid for that type.
- Simplified Code: Eliminates the need for manual type management and type checks.
- Extensibility: Easily add more types to the variant without changing the existing logic significantly.
- Ease of Use: Functions like
std::visit
provide a clean way to handle different types without repetitive code.
In summary, std::variant
is a powerful tool for managing variables that can hold multiple types, ensuring type safety and making the code easier to maintain and extend.
std::variant
is particularly useful in scenarios where a variable or a function needs to work with multiple types of data, but only one type at a time. It ensures type safety and eliminates the need for manual type management that comes with traditional unions or void*
pointers. Here are some real-world examples where std::variant
proves beneficial: