C++ variadic templates
Kent - June 11, 2016
Variadic templates
Variadic templates are templates accepting any number of template parameters.
To understand the need for variadic templates
, we’ll have to look at the history. As far as my research can tell, the first C compiler to support variadic methods
was the WatCom C compiler from 1990s the first to support variable number of elements.
The concept of having variable number of elements, is much older. It’s a mathematical term for functions taking any number of parameters, like the sum of a group of numbers.
Previously, the most used variadic methods
are the printf
-family of methods. Those are used for output formatting of strings.
// Warning: Ancient code ahead
int answer = 42;
printf("The answer is: %i", answer);
This will print The answer is: 42
.
printf
can have any number of arguments after the first argument.
The problem with variadic functions
is type safety, or the lack thereof.
Variadic methods are inherently dangerous and it’s hard to get correct results with all cases of possible exploits.
The printf
function signature is int printf(const char * format, ...);
.
With C++11, the weakness with variadic methods
are addressed with the introduction of variadic templates
.
Unless great care is taken when writing C-style variadic methods, there is a great risk of using memory not intended for this particular method. printf
family of methods, specify formatters in the first argument, and a number of arguments to format into the string. If the number of elements in the format string is greater than the supplied, a poorly written printf
method may use memory after the method call. That memory may or may not contain passwords or other sensitive information.
##Variadic templates
As with regular templates, there are both variadic class templates, and variadic function templates.
Variadic templates use the ellipsis (...
) as the signature “keyword”.
// Variadic method
template<typename ... T>
void variadic_method()
{}
template<typename ... T>
class variadic_class
{};
Both of these do nothing, and can accept any number of template parameters. Either way, it only serves as a curious case, and it will be optimized away in any compiler.
sizeof…
There is a new sizeof()
with the introduction of variadic templates, namely sizeof...()
.
It will return the number of variadic elements.
template<typename ...T>
size_t number_args(T ... args)
{
return sizeof...(T);
}
Used by this sample code
int a = 0;
double b = 0;
char c = 0;
char d[1024] = { 0 };
std::cout << "Number of variadic args:\n" <<
number_args() << "\n" << // No arguments
number_args(a) << "\n" << // One argument
number_args(a, b) << "\n" << // Two
number_args(a, b, c) << "\n" << // Three
number_args(a, b, c, d) << "\n" << // Four
number_args(a, b, c, d, a) << "\n"; // Five
It will print:
Number of variadic args:
0
1
2
3
4
5
The value is computed at compile time, and can be used in constant expressions (constexpr
).
Variadic method in use
There are a couple of usage cases for variadic templates, and most of them are when there is a class acting as a proxy method between the calling code and the consumed class itself. A proxy can be a smart pointer (std::unique_ptr
), or any other custom class encapsulating any other class.
Variadic template methods are used to construct these proxy classes by forwarding the arguments to the constructor. The unique_ptr
is an excellent example of such a method.
class to_be_made_unique
{
public:
explicit to_be_made_unique(int a, std::string & b, double f){}
};
void make_unique_ptr()
{
int a = 0;
std::string b = "b";
double c = 3.14;
auto ptr = std::make_unique<to_be_made_unique>(a, b, c);
}
This example doesn’t show it, but it beautifully joins several C++11 features into one simple use case. There is nothing special about to_be_made_unique
. It’s a regular class with a regular constructor.
The line with auto ptr = std::make_unique<to_be_made_unique>(a, b, c);
is where the beauty starts.
- It will create a variable called
ptr
, which is declaredauto
. The compiler will at compile time, deduceauto ptr
tostd::unique_ptr<to_be_made_unique> ptr
. std::make_unique
is called with template parameterto_be_made_unique
and argumentsa, b, c
.- Behind the scenes,
std::make_unique
is a method accepting a variable number of parameters, which it will perfectly forward to theto_be_made_unique
constructor, through variadic expansion. - Automatic resource cleanup. As
ptr
is a smart pointer, it will automatically delete the pointer when it goes out of scope.
And the best part of this, is that all this extra syntactic sugar will almost not incur any extra overhead at runtime. Most of this will make the compiler work a little extra, and it’ll produce cleaner machine code. The only extra cost, is the automatic deletion of the pointer when the smart pointer goes out of scope.
There is also no difference in runtime size when using std::unique_ptr
versus a raw pointer. They will either be 32-bits or 64-bits, depending on the architecture you’re building for.
Variadic expansion
To be able to use variadics, one must understand how variadic expansion works.
This is a variadic method, which will then expand the arguments and call an appropriate method.
template<typename ... T>
void variadic_expansion(T...args)
{
do_args(args...);
}
variadic_expansion
can accept any* number of arguments, and will expand the arguments into do_args
.
*) Visual C++ 2015 have a limit on 1024 template parameters, while G++ doesn’t have any practical limits. The C++ standard has a minimum limit on 1024 template parameters.
When calling the method variadic_expansion
without any parameters, it will expand args...
into no parameters. With one parameter, it will expand it into one parameter.
Here is a table of the variadic expansion happening.
int a = 0;
variadic_expansion(); // Forwarded as do_args()
variadic_expansion(a); // do_args(a)
variadic_expansion(a, a); // do_args(a, a)
It is also possible to expand the variadic pack with context.
template<typename ... T>
void var_exp2(T...args)
{
do_args((1 + args)...);
}
If we’re calling var_exp2(a, a)
, it will be expanded to call do_args(1+a, 1+a)
, which in turn is shortened to do_args(1+0, 1+0)
.
var_exp2
and var_exp3
behave identical.
template<typename ... T>
void var_exp3(T...args)
{
do_args(1 + (args)...);
}
Storing the variadic pack without expanding it
Before C++11, there was only one readily available structure in the Standard to hold generic data, std::pair
and it could only hold two values.
With C++11 comes std::tuple
. It can hold any number of elements and is constructed by a variadic initializer through std::make_tuple
. A tuple is a data structure consisting of multiple parts. It is comparable to a struct
, but a field in a tuple is accessed by an index and not a name. A struct
and a std::tuple
with identical data, occupy identical space.
#include <tuple>
int a = 0;
double b = 1;
std::string c = "abc";
auto values0 = std::make_tuple(); // Empty tuple
auto values1 = std::make_tuple(a); // Tuple with one
auto values3 = std::make_tuple(a, b, c); // Tuple with three
Empty tuples are allowed, and using a std::tuple
is sort of a generic storage class. There is no need to implement one. But if there is a need of specializing such a generic storage class, here is a sample class.
template<typename ... T>
class generic_storage
{
public:
generic_storage(T && ... args)
: m_Storage<T..>(args...)
{
}
private:
std::tuple<T...> m_Storage;
};
To create such a class, use a free method which returns the generic storage class with template parameters.
template<typename ... T>
constexpr generic_storage<T...> make_generic_storage(T && ... args)
{
return generic_storage<T...>(args...);
}
There is not any real difference from using std::make_tuple
. The only difference is that you’re wrapping the tuple in your own class, which you can build further restrictions upon.
By
struct data
{
int key;
std::string value;
double factor;
};
int key = 42;
std::string value = "Always remember to bring a towel";
double factor = 3.14;
auto tuple_data = std::make_tuple(key, value, factor);
auto struct_data = data{ key, value, factor };
std::cout << "Tuple size: " << sizeof(tuple_data) << "\n"
<< "Struct size: " << sizeof(struct_data) << "\n";
Output is:
Tuple size: 56
Struct size: 56
Tuples are strongly typed
Even though tuples aren’t the focus of this chapter, there will be a brief section with how to use tuples. Tuples are strongly typed, and the length and types are checked at compile time.
To retrieve values from tuples, there is a method called std::get
. The type returned from the tuple will be the same as we put into the tuple in the first place (except references have been stripped away, and so on).
int a = 0;
auto value = std::make_tuple(a);
auto from_a = std::get<0>(value);
The variable from_a
will have type int
because the first type inserted into the tuple is an int
.
If we pass on a const reference type to the tuple, the type returned is the type itself without any mention of const or reference.
const int & b = a;
auto tuple2 = std::make_tuple(b);
auto from_b = std::get<0>(tuple2);
The variable from_b
is of type int
, even though it’s constructed from a const int &
.
Variadic function signature
When designing and implementing a variadic method, it’s important to know what’s really happening behind the scenes. When calling a variadic method with variadic_method(a, b, c)
, the (a, b, c)
-part is packed into a pack in the method you’re calling.
Given this method signature:
template<typename ... T>
void variadic_method(T...args)
The parameters a, b, c
are packed into args
, which is a variadic pack. In the method above, the variadic pack is copied before it’s used. Methods in the standard library is using universal reference to forward the parameter pack.
To test the notion of copied vs. moved parameter packs, here is a class not_trivially_copyable
which will print something when it’s constructed, copy constructed, move constructed and destructed.
class not_trivially_copyable
{
public:
not_trivially_copyable()
{
std::cout << "not_trivially_copyable ctor\n";
}
not_trivially_copyable(const not_trivially_copyable & rhs)
{
std::cout << "not_trivially_copyable copy ctor\n";
}
not_trivially_copyable(not_trivially_copyable && rhs)
{
std::cout << "not_trivially_copyable move ctor\n";
}
~not_trivially_copyable()
{
std::cout << "not_trivially_copyable dtor\n";
}
};
Just declaring a variable of this class will print not_trivially_copyable ctor
. When it goes out of scope it will print not_trivially_copyable dtor
.
Here is method called copy_pack
, showing how the not_trivially_copyable
class will be copied when the parameter pack is copied by value.
template<typename ... T>
void copy_pack(T...args) {}
void test_copy_pack()
{
not_trivially_copyable a;
copy_pack(a);
}
Output from copying the parameter pack will be:
not_trivially_copyable ctor
not_trivially_copyable copy ctor
not_trivially_copyable dtor
not_trivially_copyable dtor
The method move_pack
will move construct the variadic parameter. The compiler will only construct and destruct one object.
template<typename ... T>
void move_pack(T&&...args) {}
void test_move_pack()
{
not_trivially_copyable a;
move_pack(a);
}
Output from moving the parameter pack is:
not_trivially_copyable ctor
not_trivially_copyable dtor
You might wonder why isn’t the move constructor called. It’s a combination of copy elision and the fact that it’s the actual parameter pack which is moved, and not the class itself.
In most cases, you’d want to use the move constructor with variadic templates, to the function signature should resemble the following.
template<typename ... T>
void variadic_method(T&&..args) {}
See Also
- C++ Concepts: New keywords
- C++ std::sort predicate with templates
- C++ variadic template example
- Default explicit equality operator
- Mistakes