This is a rewrite from an article I wrote in french.
https://medium.com/@antoinehuot1995/note-sur-les-variadic-templates-en-c-51c1bacade01
The power of a language like C++ is undoubtedly tied to many factors and decades of research, particularly with the arrival of the STL and various standards. In a conversation with a colleague (who has been in the development industry since before I was even born), I realized that templates in C++ are deeply rooted in the core of the language, being part of its semantics since version 98!
A Quick Reminder on Templates
I’m far from being a pro at using the complex metaprogramming tools that templates offer. Here, I’ll simply provide a quick reminder about the basic function of templates in C++. In essence, a template (or a function of type template) is simply a way to define a function that can be used without being strictly tied to a single type.
To demonstrate the idea of templates, let me present a simple example. Suppose we want to define and declare a function that adds two integers together:
int add(int a, int b) {
return a + b;
}
While this function is very useful for adding two integers, it becomes limited when we want to generalize it to work with other types, such as floating-point numbers. For instance, if you were writing software for a cash register and needed to handle both integers and decimals, you might be tempted to write another function for decimals:
double add(double a, double b) {
return a + b;
}
Now we have similar code for two types (int and double), but not for others like float
. What if you wanted to add three or four numbers of varying types? Maintaining such code becomes cumbersome.
Using Templates to Generalize
Here’s how we can use templates to define a function that adds two variables of any type:
template <typename T>
T add(T a, T b) {
return a + b;
}
This code illustrates the use of templates, allowing developers to use generic functions or classes without worrying about manually specifying the types. The compiler takes care of generating the appropriate code based on the arguments passed.
Understanding the Code
template <typename T>
: This makes the function generic, enabling it to work with various types without explicitly defining them.- In
main()
, you can call theadd
function with different types, likeint
ordouble
.
This abstraction frees you from managing parameter types, allowing you to focus on functionality.
Is the Compiler Just Copying Code?
Yes and no—not exactly. Templates abstract parameter types, but the compiler doesn’t simply copy-paste code and adjust types. Instead, it adds substantial logic at the parser and lexer levels, especially for complex examples.
To better understand how templates are handled at the compiler level, consider analyzing the generated assembly code using GCC:
gcc -S -O1 {filename.cpp}
First Variadic Template
Let’s create a simple display()
function to print a string using std::cout
. Inspired by The Avalanches’ album (one of my favorite bands), I’ll use the default behavior of printing the text “If I was a Folkstar”:
void display(const std::string& message = "If I was a Folkstar") {
std::cout << message << std::endl;
}
Making It More Flexible
Now, let’s modify the function to accept any number of arguments:
template <typename T, typename... Args>
void display(T first, Args... rest) {
std::cout << first << std::endl;
display(rest...);
}
Here:
...
represents a parameter pack, allowing multiple types or templates.- The function recursively prints each argument in
rest
.
In main()
, we can now call the function like this:
int main() {
display(1, 44, 2.656, "Hello", "Golden Retriever");
return 0;
}
This outputs each argument on a new line.