Variadic templates, part 3 (or how I wrote a variant class)

February 15, 2012

Now that I have explained r-value references and variadic templates (part 1 and part 2), I will explain in great detail a stack based replacement for boost::variant. The only assumption that needs to be made here is that all of the objects contained in it have a nothrow move constructor.

A variant is essentially a tagged union. It is a variadic template class which holds one of the types specified in the template. The interface to the class is then designed in such a way that it is impossible to get a type error at runtime. If a value of the wrong type is requested, either nullptr is returned or an exception is thrown, depending on the version of get.

The variant interface

The interface to a variant is pretty simple, all you can do with a variant is construct it with different objects. On top of that, there are two functions, one to retrieve values from the variant, the other calls an appropriate visitor depending on the type of whatever is held in the variant. The Variant has two template parameters, the first is there because a Variant must hold at least one thing, the second is a variadic template pack which holds the remainder of the types that can be held.

Both of these functions will call visitable.operator() on the object held in the Variant. The second passes the argument as a const reference. Any class used as Visitable must have operator() defined that can take everything contained in the Variant. Furthermore, each function in the visitor must return the same thing, and the return type is typedef’ed as result_type. Additionally, arbitrary extra arguments can be passed to the visitor functions. For example, suppose that we had the following variant object:

There are a few things going on here all at once, the main point is that we want a field m_storage to be an array big enough to hold the biggest object, and we need it to have the correct alignment for the object with the biggest alignment.
The union will be aligned to the alignment of its biggest member, so m_align is simply ensuring that m_storage has the correct alignment. Both the alignment and the size are computed with the metafunction max. I won’t show its code here because it is long and distracts from the Variant, but you can get it in the supplied files. It takes as arguments a metafunction which is applied to the remainder of the arguments, and the biggest type as determined by the metafunction defines the result.

Recursive Wrapper

To store tree-like structures, where some objects could have one or more members of the same Variant, there is a class called recursive_wrapper, which essentially stores a pointer to the object instead of the actual object. There is a little bit of magic to make all of this work transparently which I will describe in the next section on visiting.

An example use of recursive_wrapper might be to store a binary tree that only has values at the leaves:

Visiting

Everything else in the Variant is implemented with visitors, so I will explain that next. This is also where most of the magic is, so you should pay particular attention to this part to understand the details.

The first thing that apply_visitor does is to call Variant::apply_visitor, there are two versions of this, one for a non-const Variant, one for a const Variant. They both do exactly the same thing, which results in the constness being captured in the template parameters rather than being explicit. From this point onward we only need one version of everything.

The template argument Internal is for the recursive_wrapper. When doing some of the internal visitors, we want the recursive_wrapper type, not the type that it’s holding. I will explain that in more detail later.

The class do_visit is defined as a template class, with a templated operator(), this is so that it can have two sets of template arguments, the set of types, and the arguments that the visitor will pass. It’s definition is as follows:

There is a lot here, but it doesn’t really do much. The magic is all in the static array callers. There is an entry for every possible type that can be stored in the Variant, and each entry is a function which calls the visitor, after casting the storage to the appropriate type, with the object and the arguments supplied. So what we have is a different array for every combination of types, visitors, and arguments that are called on any Variant. Then we simply call the appropriate function, indexing it with the which variable.

Most of the functionality of the variant is implemented with the visitor—copy
construction, move construction, destruction, assignment, and the get function. The only thing left is initialisation. The reason that we need an internal visit is to handle the four previously mentioned operations. When copying and deleting an object, we need a reference to the recursive_wrapper that holds it, not just the actual object. Unlike when a user uses a Variant, they don’t care about the recursive_wrapper, the whole point is to pretend that we’re only holding exactly the types that the user wanted.

Initialising

To initialise a Variant, the appropriate tag needs to be chosen for the object. To do this, we create a template class who has an initialise function for the first type from a parameter pack, and derives from itself but instantiated with the remainder of the parameter pack. It also counts which type that it is handling. This way, calling initialise will use the normal rules of C++ to decide which overload to call. If the object being initialised isn’t unambiguously convertible to one of the types, then that is a user error.

The get function

Lastly, we have the get function. This allows the user to retrieve a value from the Variant in a typesafe manner. One can request a type from the Variant, and if that is not what is being held, then an exception will be thrown, or a null pointer returned, depending on the version of get.

The get function uses the class get_visitor. This is a visitor which checks if the type we want is the type contained in the Variant. Its definition is below:

Here is one of the versions of the get function to show how the visitor is called. The idea is that if get_visitor::operator() matches the type requested, then the object is returned, otherwise nullptr is returned.

Nice work!
Could you maybe make the code sections a bit larger? It is quite annoying to move the underlying bar to see the whole code (I know that there is the complete code at the end, but reading the code with the comments helps) :)