C++17: std::variant<>

Written by on November 5, 2017 in C++, Programming with 0 Comments

C++17 introduces a new type-safe union in the form of std::variant. At any time, it can store a single value from one of many types. We need to include <variant> to use this feature.

Let us look at a simple example to get started:

Example1

Example1

First, we define a variant object v1 to hold either an int or string type, and initialize it with an integer value. The alternative elements can each be accessed by an index value, starting from 0. The left-most alternative gets zero, the next one gets 1, and so on. In this example, int will corrspond to index 0 and string will get index 1. The index() member function returns the index for the valid alternative.

We use the std::get<>() function to reference the currently valid alternative. Since the current value is integer, we use get<int>(v1). We could also use get<0>(v1).

We can assign a value directly to the variant object, or through the active index. Note that trying to access the invalid alternative using the wrong index will  throw a run-time exception of type std::bad_variant_access. On the other hand, it is a compile-time error to use an index outside the valid range.

The correct variant alternative will be selected automatically based on the initialization expression. If we initialize with a string literal, the string alternative is selected. And in this case, the valid active index will be 1, not zero.

See the next example:

Example2

Example2

What if we do not initialize at all? In this case, the left-most alternative must allow default initialization, else it is a compile-time error. In the above example, the int element will be initialized with 0, and the index() function will return 0.

Let us try using some user-defined type. Consider the struct X given below:

User Type

User Type

Note that X does not have a default constructor. Let us consider using X as a type alternative in std::variant.

Example3

Example3

The first definition (commented out) does not compile because we are depending on default initialization and X, being the left-most type, does not have a default constructor.

When we change the order of types, it works because int will become the active type with a default value of 0.

In the next definition, we supply a proper initialization value for X. This results in a call to X constructor to instantiate a new object and X becomes the active type.

The next statement is interesting. We assign an integer value to the variable. Since the previous value was of type X, the compiler cannot just overwrite that area with the integer value, but it must destroy X first! This is indeed what happens, and this is the most important difference between a union and a variant. std::variant thus guarantees type safety!

We saw earlier that if we pass an incorrect index (or type) to std::get, we get std::bad_variant_access exception. Alternatively, we can  use std::get_if. In this case we have to pass a pointter to the variant object. If the index (or type) matches the active element, then a pointer to the value will be returned. If not, a nullptr will be returned. This is illustrated in the following example:

Example4

Example4

Alternatively, we can use the function std::holds_alternative() to check if the variant currently holds a value of given type. See the following example:

Example5

Example5

The next example shows how we can use variant::emplace() member function to construct an object in-place.

Example6

Example6

The library guarantees that the previous object will be destroyed before constructing the new object in its place.

The last funcationality we will discuss is std::visit. Let us suppose that we have a collection of variant objects, and we want to visit each element of the collection and do something on it. How can we do this? There are a few interesting ways, but to keep the discussion brief, we will look at a basic approach.

Let us first define a function object that has overloaded versions for the different variant alternatives (we use member template for this). We can pass the function object to the std::visit function along with the variant. This causes the appropriate function (based on the active variant alternative) to be applied on the variant element. This is shown in the following figure.

Example7

Example7

Thus std::variant is a useful concept when you are looking for a type-safe discriminated union. Check it out.

I used Visual Studio Profession 2017, Version 15.4.0 for this article. The source code can be downloaded from here.

Tags: ,

Subscribe

If you enjoyed this article, subscribe now to receive more just like it.

Subscribe via RSS Feed

Leave a Reply

Your email address will not be published.

Top