Concepts / Template Parameters and Type Deduction

Template Parameters and Type Deduction

Variadic templates accept a variable number of arguments of a variety of types.

  • Programming

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

Definition

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
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.

cpp
Output
1
a
2.5
Notes on the code
  • 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
print_all(1, 'a',2.5)prints 1print_all('a', 2.5)prints aprint_all(2.5)prints 2.5print_all()base casepeels off 1peels off 'a'peels off 2.5
When print_all(1, 'a', 2.5) is called, each recursive call peels off one argument, and the empty call 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.

cpp
Output
10
Notes on the code
  • 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
Variadic templatetoolDifferent types perargumente.g. int, char, doubleInitializer listtoolSame type for allargumentse.g. all intfits whenfits when
A variadic template suits arguments of different types, while an initializer list suits arguments that all share one type.
QuestionVariadic templateInitializer list
Argument typesMay differ from argument to argumentAll share one type
What the function receivesA parameter pack, expanded by recursionOne parameter holding a braced group
Typical fitMixed inputs such as int, string, and doubleA 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 like

    A function that sums any number of ints is written as a variadic template.

    Why it fails

    The 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 instead

    Check 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 like

    Assuming print_all(1, 'a', 2.5) treats all three values as one type.

    Why it fails

    Each argument keeps its own type. The pack holds the values as they were passed.

    Do this instead

    Treat 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

EASY

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.
MEDIUM

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.

Related concepts

More concepts starting with T