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.
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
Tpassed 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.
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::tupleis 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:
- Link Implementation: The purpose of this implementation is to extract the front T node, and pass the remaining Ts... onto the next link.
- 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.
Now that we can create an instance of our container, you'll immediately notice that if you print out the
valuefield, it will output the
std::stringinstance 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
valuefield. This is somewhat difficult to explain accurately, so here's an example to clarify:
Based on the above code to extract the desired
valuefrom 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
Here again, we're using the same pattern as we did in the first
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
1each depth. The idea is that once the index is
0, then the typedefs will point to the correct container type and
The last step into turning this into a generic utility for use with our
container<T, Ts...>class is to create a
getmethod that takes
int index, class Ctemplate parameters, define the return type as
typename container_index, and then perform the
static_castin the method body and return the
value. This will look something like this:
Now that we have our
getmethod, 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:
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::tuplefor 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
Tcould 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.