Template Parameters and Type Deduction
Variadic templates accept a variable number of arguments of a variety of types.
A Function That Takes Any Number of Inputs
Once you learn that a template can accept a variable number of arguments, it is tempting to use that feature everywhere a function takes several inputs. A function that adds up any number of integers, or prints any number of names, can look like a natural candidate. This article follows one function through a short sequence of traces: first a call with mixed argument types, then a call with a single type. Along the way it explains what variadic templates are actually for, why the tempting shortcut is a misconception, and how to decide which tool fits a given function.
Why Argument Count Is Not Enough
A variadic template is a template that accepts a variable number of arguments of a variety of types.
The number of arguments alone does not justify a variadic template. The deciding question is whether the types matter. Variadic templates are suited to functions whose arguments have different types. A single call might pass an integer, a string, and a floating-point number. No single fixed parameter list describes all three at once, and a variadic template lets one function accept the whole group. Each argument keeps its own type: the variadic template does not force the three values into one common type. A regular template with a single type parameter could not describe that call, because the arguments do not share a type.
Tracing a Mixed-Type Call
Consider a function that prints every argument it receives, with each argument on its own line. The call passes an integer, a character, and a floating-point number. Before reading the trace, decide what the output should look like.
What do you think happens?
What order will the three values print in, and what type does each one keep?
Reveal answer
The values print in the order they were passed: 1, then a, then 2.5. Each keeps its own type: int, char, and double.
The variadic template does not convert the three values to a common type. The first argument is peeled off, printed, and the remaining arguments are passed on to the next call.
1
a
2.5- typename... Rest declares a parameter pack: a group of template arguments of varying types
- T first captures the first argument, whatever its type
- Rest... rest captures the remaining arguments as a pack
print_all(rest...)expands the pack into a call that receives one fewer argument- The empty
print_all()overload is the base case that stops the recursion
How the Pack Is Peeled Apart
The typename... Rest in the template header declares a parameter pack. A pack gathers the remaining arguments, which may have different types, into one group. The expression rest... expands that pack back into individual arguments when it is passed on. In the trace above, the first call captures 1 as first and the pack holds 'a' and 2.5. The call print_all(rest...) then supplies 'a' and 2.5 to a new call, which captures 'a' as first and 2.5 as the new pack.
The recursion needs a stopping point. Once the pack is empty, the call print_all() matches the non-template overload, which does nothing. Without that base case, the recursion would have no defined end. Notice also that the types are never merged: the call that handles 2.5 treats it as a double, and the call that handles 'a' treats it as a char. Each position in the argument list keeps its own deduced type.
Tracing a Single-Type Call
Now change the requirement. Suppose a function must add up any number of integers, and every argument is an int. The source is direct on this case: when every argument has the same type, do not use a variadic template. Use an initializer list instead. An initializer list lets a caller supply a braced group of values of one type, and the function receives them together as a single parameter. The function then only has to handle one type.
10- std::
initializer_list<int> is one parameter that receives the whole braced group - Every element has the same type, so one loop handles all of them
- The caller writes the values once inside braces, with no recursion needed
| Question | Variadic template | Initializer list |
|---|---|---|
| Argument types | May differ from argument to argument | All share one type |
| What the function receives | A parameter pack, expanded by recursion | One parameter holding a braced group |
| Typical fit | Mixed inputs such as int, string, and double | A list of integers that all need to be summed |
Deciding which tool fits a given function
Mistakes When Choosing a Tool
Common mistake Using a variadic template because the function takes several inputs
What it looks likeA function that sums any number of ints is written as a variadic template.
Why it failsThe number of arguments alone does not justify a variadic template. When every argument shares one type, the source directs you to an initializer list.
Do this insteadCheck the types first. If they are all the same, use std::initializer_list.
Common mistake Expecting a variadic template to merge mixed arguments into one type
What it looks likeAssuming print_all(1, 'a', 2.5) treats all three values as one type.
Why it failsEach argument keeps its own type. The pack holds the values as they were passed.
Do this insteadTreat each argument as having its own type, and let the recursion handle one position at a time.
Common mistake Forgetting the base case, so the recursive unpacking has no defined end
Deciding Which Tool Fits
A function must print a name, an age, and a height, where the name is a string, the age is an integer, and the height is a floating-point number. Should this function use a variadic template or an initializer list? Explain your choice by naming the types of the arguments.
Hints
- Look at whether the arguments share one type.
- Recall that each argument keeps its own type when a variadic template is used.
A function must sum any number of double values passed by the caller. Would you use a variadic template or an initializer list, and why? Write the parameter type you would declare.
Hints
- All of the arguments share the type double.
- The source directs homogeneous argument lists toward initializer lists.
Key Takeaways
- A variadic template accepts a variable number of arguments of a variety of types, and each argument keeps its own type.
- The number of arguments alone does not justify a variadic template; the types decide the choice.
- A parameter pack gathers the remaining arguments, and recursion peels them off one at a time until an empty base case stops the calls.
- For homogeneous argument lists, where every argument has the same type, use an initializer list rather than a variadic template.