Sunday, April 13, 2014

C++11 Variadic Templates

In recent news, I've been revisiting my programming roots and tinkering with some of the latest C++11 has to offer. There are many new awesome features, including variadic templates. These templates give you the ability to define an arbitrary list of types in your template parameters. This removes the need to define N template parameters using specialization. Consider the following naive example tcontainer:

Besides being horrific to look at and a pain to maintain, it limits the implementer to, in our case 4, types. This type of implementation always reminds me the first time I realized that the Action<T> was implemented with 16 variations. Yikes.


Varia-wat?


Now there is a syntax that allows us to compress the N types down to a single "Packed Parameter" using the splat (...) syntax. Here is a very basic example:

Note that we are essentially dequeing the first element (T) and recursively (kind of) passing the rest of the arguments until there is only one argument left. The other wonderful thing about templates is if any T passed to the print method doesn't have an overloaded stream operator <<, then the error is compile-time. If we were to call this method, here's a very simplified view of how it might expand:

A moment ago, I mentioned that the call to print<T,Ts...> was kind of recursive. While it certainly has recursive appearance and qualities, the compiler is going to treat each variation as its own method. The most important aspect of meta-programming to remember is that templates are resolved at compile time. Personally, I'm constantly having to remind myself that template resolution must be known ahead of time.


Container (Tuple)


Let's take a look at another example, this time using a class/struct. For all intents and purposes, you could really call this implementation a basic tuple. The std::tuple is a much more complete design, but it can be quite intimidating to look at for learning purposes. So, let's create a basic container class which will store a list of Ts... instances. Just like in our previous example, we'll need to create two basic implementations:

  1. Link Implementation: The purpose of this implementation is to extract the front T node, and pass the remaining Ts... onto the next link.  


  2. Terminator Implementation: The purpose of this implementation is to halt the "recursion." In other words, we want to continue to sub-class our container<class T, class... Ts> implementation until we have one remaining T instance. This implementation specializes the container to do just that.  



Now, let's create a factory method so we can let type inference do it's thing.


Access value Fields


Now that we can create an instance of our container, you'll immediately notice that if you print out the value field, it will output the std::string instance in the first element of our container creation ("hello"). Since our container<T, Ts...> class uses the same field name (value) for each sub-class, then in order to extract a specific value of type T, we'll need pick the subclass that matches our desired value, cast our container, and then access the value field. This is somewhat difficult to explain accurately, so here's an example to clarify:


Indexed Approach


Based on the above code to extract the desired value from our container, it seems that an implementer would have to know a lot about the implementation details of the container in order to actually use it. Let's provide an additional utility that will allow us to specify an index representing which type we'd like to access. We'll need to create two more template classes such that we can dive into the subclasses, and reduce the index until we hit 0. Just like our terminator class for container, we'll also need a specialized template for the 0-case.


Here again, we're using the same pattern as we did in the first print example and with our container<T, Ts...> class, which is the use of "recursive" access. The termination with this case is dependent on the value of int index, which is decreased by 1 each depth. The idea is that once the index is 0, then the typedefs will point to the correct container type and T-type.


The last step into turning this into a generic utility for use with our container<T, Ts...> class is to create a get method that takes int index, class C template parameters, define the return type as typename container_index::type, and then perform the container_type lookup and static_cast in the method body and return the value. This will look something like this:


Implementation


Now that we have our get method, we can use it to extract the specific values. Also, remember that templates are a compile-time construct, so incorrectly assuming a type will yield a compile-time error (a helpful one at that!"). Here's the example use:



Final Thoughts


Hopefully this walk-through has proven helpful if you are new to exploring the newer C++11 features. It's extremely important to note that I left out a lot of "conventional" C++ for the sake of clarity. I wouldn't suggest using the container<T, Ts...> for anything other than a guide. As mentioned previously, you should use std::tuple for serious use.

Admittedly, I still struggle a lot with C++ semantics, especially as it pertains to the newer features. This blog was a result of my struggle to find a clear set of examples for variadic templates, at least such that I felt comfortable using them. Currently, my plan was to use this feature to represent shader inputs, so if you needed to add a new input, the added T could propagate to any implementations, rather than be required to add support for the new type anywhere it was needed. Point being, don't limit the use of this amazing feature to an arbitrary type/value list!

All the code from the examples can be downloaded here: variadic_templates.tar.gz.

The specific version of the compiler I used for these examples:

I'm also nearly positive that Visual Studio 2013 will also compile without a problem.