Ellipsis and variadic templates

This article shows how to use the ellipsis (...) with C++ variadic templates. The ellipsis has had many uses in C and C++. These include variable argument lists for functions. The printf() function from the C Runtime Library is one of the most well-known examples.

A variadic template is a class or function template that supports an arbitrary number of arguments. This mechanism is especially useful to C++ library developers: You can apply it to both class templates and function templates, and thereby provide a wide range of type-safe and non-trivial functionality and flexibility.

Syntax

An ellipsis is used in two ways by variadic templates. To the left of the parameter name, it signifies a parameter pack, and to the right of the parameter name, it expands the parameter packs into separate names.

Here's a basic example of variadic class template definition syntax:

template<typename... Arguments> class classname;

For both parameter packs and expansions, you can add whitespace around the ellipsis, based on your preference, as shown in this example:

template<typename ...Arguments> class classname;

Or this example:

template<typename ... Arguments> class classname;

This article uses the convention that's shown in the first example (the ellipsis is attached to typename).

In the preceding examples, Arguments is a parameter pack. The class classname can accept a variable number of arguments, as in these examples:

template<typename... Arguments> class vtclass;

vtclass< > vtinstance1;
vtclass<int> vtinstance2;
vtclass<float, bool> vtinstance3;
vtclass<long, std::vector<int>, std::string> vtinstance4;

By using a variadic class template definition, you can also require at least one parameter:

template <typename First, typename... Rest> class classname;

Here's a basic example of variadic function template syntax:

template <typename... Arguments> returntype functionname(Arguments... args);

The Arguments parameter pack is then expanded for use, as shown in the next section.

Other forms of variadic function template syntax are possible—including, but not limited to, these examples:

template <typename... Arguments> returntype functionname(Arguments&... args);
template <typename... Arguments> returntype functionname(Arguments&&... args);
template <typename... Arguments> returntype functionname(Arguments*... args);

Specifiers like const are also allowed:

template <typename... Arguments> returntype functionname(const Arguments&... args);

As with variadic template class definitions, you can make functions that require at least one parameter:

template <typename First, typename... Rest> returntype functionname(const First& first, const Rest&... args);

Variadic templates use the sizeof...() operator (unrelated to the older sizeof() operator):

template<typename... Arguments>
void tfunc(const Arguments&... args)
{
    constexpr auto numargs{ sizeof...(Arguments) };

    X xobj[numargs]; // array of some previously defined type X

    helper_func(xobj, args...);
}

More about ellipsis placement

Previously, this article described ellipsis placement that defines parameter packs and expansions in this form: "to the left of the parameter name, it signifies a parameter pack, and to the right of the parameter name, it expands the parameter packs into separate names". While technically true, it can be confusing in translation to code. Consider:

  • In a template-parameter-list (template <parameter-list>), typename... introduces a template parameter pack.

  • In a parameter-declaration-clause (func(parameter-list)), a "top-level" ellipsis introduces a function parameter pack, and the ellipsis positioning is important:

    // v1 is NOT a function parameter pack:
    template <typename... Types> void func1(std::vector<Types...> v1);
    
    // v2 IS a function parameter pack:
    template <typename... Types> void func2(std::vector<Types>... v2);
    
  • Where the ellipsis appears immediately after a parameter name, you have a parameter pack expansion.

Example

A good way to illustrate the variadic function template mechanism is to use it in a rewrite of some of the functionality of printf:

#include <iostream>

using namespace std;

void print() {
    cout << endl;
}

template <typename T> void print(const T& t) {
    cout << t << endl;
}

template <typename First, typename... Rest> void print(const First& first, const Rest&... rest) {
    cout << first << ", ";
    print(rest...); // recursive call using pack expansion syntax
}

int main()
{
    print(); // calls first overload, outputting only a newline
    print(1); // calls second overload

    // these call the third overload, the variadic template,
    // which uses recursion as needed.
    print(10, 20);
    print(100, 200, 300);
    print("first", 2, "third", 3.14159);
}

Output

1
10, 20
100, 200, 300
first, 2, third, 3.14159

Note

Most implementations that incorporate variadic function templates use recursion of some form, but it's slightly different from traditional recursion. Traditional recursion involves a function calling itself by using the same signature. (It may be overloaded or templated, but the same signature is chosen each time.) Variadic recursion involves calling a variadic function template by using differing (almost always decreasing) numbers of arguments, and thereby stamping out a different signature every time. A "base case" is still required, but the nature of the recursion is different.