Skip to content

Variant

Variant

The class template std::variant (C++17) represents a type-safe union. An instance of std::variant at any given time either holds a value of one of its alternative types, or in the case of error - no value.

If we define a variant v including lots of types, including booleans and lists, this would be somewhat similar to:

  • PHP: $v = 3.14; // see https://bit.ly/3cVuJvb
  • Python: v = 3.14;
  • Javascript: var v = 3.14;

As with unions, if a variant holds a value of some object type T, the object representation of T is allocated directly within the object representation of the variant itself. Variant is not allowed to allocate additional (dynamic) memory.

A variant is not permitted to hold references, arrays, or the type void. Empty variants are also ill-formed (std::variant can be used instead).

A variant is permitted to hold the same type more than once, and to hold differently cv-qualified versions of the same type.

Consistent with the behavior of unions during aggregate initialization, a default-constructed variant holds a value of its first alternative, unless that alternative is not default-constructible (in which case the variant is not default-constructible either). The helper class std::monostate can be used to make such variants default-constructible.

1
2
3
4
5
union my_union {
    double a;
    char b;
    int c;
};

1
2
3
4
5
// - It only works with fundamental data type
// - It doesn't have convenience functions
my_union u = {3.14};
u = {'A'};
u = {15};

1
2
3
4
5
// A union is as large as the largest type in the union
std::cout << "sizeof(double): " << sizeof(double) << '\n';
std::cout << "sizeof(char): " << sizeof(char) << '\n';
std::cout << "sizeof(int): " << sizeof(int) << '\n';
std::cout << "sizeof(my_union): " << sizeof(my_union) << '\n';

1
std::variant<double, char, std::string> v;

1
2
3
v = 3.14;               // becomes double
v = 'A';                // becomes char
v = "Some longer text"; // becomes std::string

1
2
3
4
5
6
7
// A variant is larger than the largest type in the variant
// This happens because variants need a flag to indicate the current type
std::cout << "sizeof(double): " << sizeof(double) << '\n';
std::cout << "sizeof(char): " << sizeof(char) << '\n';
std::cout << "sizeof(string): " << sizeof(std::string) << '\n';
std::cout << "sizeof(variant<double, char, std::string>): "
          << sizeof(std::variant<double, char, std::string>) << '\n';

1
2
3
4
5
6
v = 3.14;
std::cout << get<double>(v) << '\n';
v = 'A';
std::cout << get<char>(v) << '\n';
v = "Some longer text";
std::cout << get<std::string>(v) << '\n';

1
2
// Applying function to whatever value type it holds
std::visit([](auto x) { std::cout << x << '\n'; }, v);

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
switch (v.index()) {
case 0:
    std::cout << "This is a double\n";
    break;
case 1:
    std::cout << "This is a char\n";
    break;
case 2:
    std::cout << "This is a string\n";
    break;
}

1
2
3
4
5
6
7
if (std::holds_alternative<double>(v)) {
    std::cout << "This is a double\n";
} else if (std::holds_alternative<char>(v)) {
    std::cout << "This is a char\n";
} else if (std::holds_alternative<std::string>(v)) {
    std::cout << "This is a string\n";
}

Share Snippets