One C++11 feature that I'm really liking is variadic templates. They make many things much easier and type-safe. I have actually got to use them in actual production code, which is nice.
Basically what variadic templates allow is to create functions (or classes) that take any amount of parameters. The difference with C functions (which use the "..." operator as parameter) is that the amount of parameters is determined at compile time, and you can implement full type checking (so that if any of the parameters is of an unsupported type, you will get a compile error rather than the program crashing when run, giving garbage as result, or even, in the best case scenarios, getting a runtime error.)
This allows for quite many things that might be immediately obvious. For example, let's assume that you have a class that takes an integral template parameter (a typical example would be an array class of some sorts, but it doesn't have to be exactly that). That is, something like:
Language: cpp
template<typename Value_t, unsigned kSize>
class MyClass
{
// something here
};
Now, wouldn't it be nice if you were able to write a member function for that class that takes exactly kSize parameters of type Value_t? Since both are known at compile time, that ought to be theoretically possible. C++98, however, provides no tools to achieve this. However, with variadic templates you can.
You can do it eg. like this:
Language: cpp
template<typename Value_t, unsigned kSize>
class MyClass
{
public:
template<typename... Parameters>
void foo(const Parameters&... parameters)
{
static_assert(sizeof...(Parameters) == kSize, "Wrong amount of parameters");
// handle 'parameters' here
}
};
Now if you have, for example, an object of type "MyClass<int, 5>", then you can call its 'foo()' function only with exactly 5 values, or else you get a compile-time error.
(The nice thing about this is that other possibilities are also available. For example suppose you want the function to take
at most kSize parameters rather than exactly that amount. It's easy enough to change the '==' check to a '<=' one. Likewise you could check that there's at least one parameter, and so on.)
"But wait", you will be asking (or probably not, unless you are really into C++),
"that doesn't actually check that the types of the parameters is right, only that their amount is." That's where the "handle 'parameters' here" comes into play.
There are actually many ways in which the parameter pack can be handled. There's no direct way of just writing a loop that goes through the parameters, but there are several other options.
You can handle then recursively, like this:
Language: cpp
void handleParameters() {}
template<typename... Rest>
void handleParameters(const Value_t& parameter, const Rest&... rest)
{
// handle 'parameter' here
handleParameters(rest...);
}
You can then make a "handleParameters(parameters);" call from inside the 'foo()' function. The type checking is done because if there's no function to handle one of the types, you'll get a compiler error.
A nice thing about this is that you can actually support several different types of parameters, even if they are incompatible. To do this, you can make the above function take a templatized type as its first parameter, and then have overloads of a 'handleParameter()' function for each supported type, which the function above calls. You can write an overload for each supported type.
If copying the parameters is expected to be efficient, or if efficiency is a complete non-issue, then there's a simpler trick that can be used to handle those parameters. And that's by creating an array like this:
Language: cpp
template<typename... Parameters>
void foo(const Parameters&... parameters)
{
static_assert(sizeof...(Parameters) == kSize, "Wrong amount of parameters");
Value_t buffer[] = { parameters... };
// Now we can traverse 'buffer' here
}
The parameter pack expansion is surprisingly complex. It is, in fact, possible to write code like this:
Language: cpp
template<typename... Coordinates>
void foo(const Coordinates&... coordinates)
{
Node* nodes[] = { createNode(this, coordinates, 1, 0)... };
// ...
}
(where 'createNode()' is simply a regular function that takes some regular parameters.)
What happens here is that the entire subexpression is expanded for every parameter (and effectively separated by parameter-commas.)