C++ Variadic Templates

1. ConceptC++ Variadic Templates are a core feature introduced in C++11 that allows templates (class templates or function templates) to accept zero or more parameters of any type / non-type, overcoming the limitation of traditional templates that can only have a fixed number of parameters. The core concepts are “parameter packs” and “pack expansion,” enabling highly generic code (such as the underlying implementations of standard library components like <span>std::tuple</span> and <span>std::printf</span>). The essence of variadic templates lies in two key syntaxes: parameter packs and pack expansion, implemented using the operator <span>...</span> (<span>...</span> is the “soul” of variadic templates).1. Parameter Pack

  • Definition: A “container” that receives multiple parameters, which can be divided into two types:
    • “Template Parameter Pack”: A collection of parameters at the template level (e.g., <span>typename... Types</span>);
    • “Function Parameter Pack”: A collection of parameters at the function level (e.g., <span>Targs... args</span>).
  • Syntax:<span>typename...</span> (type parameter pack) or <span>auto...</span> (non-type parameter pack, C++17+), immediately followed by the parameter name (e.g., <span>Types</span>, <span>Targs</span>).

2. Pack Expansion

  • Definition: Expands all parameters in the parameter pack into independent parameters (for example, expanding <span>args...</span> into <span>a, b, c</span>).
  • Syntax: Add <span>...</span> after the parameter pack name (e.g., <span>Types...</span>, <span>args...</span>).
  • Function: The parameter pack itself cannot be used directly (cannot be iterated or passed directly), it must be split using “pack expansion”.

2. Two Forms of Variadic TemplatesVariadic templates are divided into “class templates” and “function templates,” with slightly different usage but consistent core logic.

1. Variadic Class Templates

Allow classes to accept any number of type / non-type parameters, a typical example is <span><span>std::tuple</span></span> (which can store elements of any type and any number).

Syntax Format
// Type parameter pack (receives multiple types)template<typename... Types>  // Types: type parameter pack (0 or more types)class MyTuple {};// Non-type parameter pack (C++17+, receives multiple non-type values, such as integers, pointers, etc.)template<auto... Values>     // Values: non-type parameter pack (0 or more non-type values)class MyValues {};
Instantiation Examples
// 1. Type parameter pack instantiationMyTuple<> t0;                // Valid: 0 parameters (Types = empty list)MyTuple<int> t1;             // Valid: 1 type parameter (Types = {int})MyTuple<int, std::string, double> t2; // Valid: 3 type parameters (Types = {int, string, double})// 2. Non-type parameter pack instantiation (C++17+)MyValues<> v0;               // Valid: 0 non-type parametersMyValues<10, 'a', 3.14> v1;  // Valid: 3 non-type parameters (value types can differ)
Key: The Standard Library’s <span><span>std::tuple</span></span>

<span><span>std::tuple</span></span> is a typical application of variadic class templates, capable of storing multiple values of any type, essentially encapsulating variadic templates:

#include <tuple>std::tuple<int, std::string, bool> tp(10, "hello", true);// Equivalent to: template parameter pack Types = {int, std::string, bool}

2. Variadic Function Templates

Allow functions to accept any number and any type of parameters, a typical example is <span><span>std::printf</span></span> (the general printing function of the C++ standard library).

Core Issue: How to Iterate Over Parameter Packs?

The parameter pack itself cannot be directly iterated (there are no iterators), the most common method in C++ is recursive expansion:

  • “Recursive Case”: Split the parameter pack, process the first parameter, and recursively pass the remaining parameters;
  • “Base Case”: When the parameter pack is empty, terminate the recursion (to avoid infinite loops).
Syntax Format and Example (General Printing Function)
#include <iostream>// 1. Base Case (recursion termination condition): called when there are no parameters, prints the remaining contentvoid print() {    std::cout << std::endl; // Recursion ends, new line}// 2. Recursive Case (variadic function template): split parameter packtemplate<typename T, typename... Targs>  // T: first parameter type; Targs: remaining parameter type packvoid print(T first, Targs... rest) {     // first: first parameter; rest: remaining parameter pack    std::cout << first << " ";           // Process the first parameter    print(rest...);                      // Pack expansion: recursively process remaining parameters (rest... expands to multiple independent parameters)}// Testint main() {    print(10);                          // Output: 10     print(10, "hello", 3.14, true);     // Output: 10 hello 3.14 1     return 0;}
Recursive Expansion Process (taking <span><span>print(10, "hello", 3.14)</span></span> as an example)
  1. First Call:<span><span>print(10, "hello", 3.14)</span></span> → Process <span><span>10</span></span>, recursively call <span><span>print("hello", 3.14)</span></span>;
  2. Second Call:<span><span>print("hello", 3.14)</span></span> → Process <span><span>"hello"</span></span>, recursively call <span><span>print(3.14)</span></span>;
  3. Third Call:<span><span>print(3.14)</span></span> → Process <span><span>3.14</span></span>, recursively call <span><span>print()</span></span> (triggering the base case);
  4. Base Case: Print a new line, recursion terminates.

3. Core Syntax Details of Variadic Templates

1. Position Restrictions of Parameter Packs

  • The template parameter pack must be the last parameter in the template parameter list (cannot be in the middle):
// Valid: parameter pack at the endtemplate<typename T, typename... Args> class A;// Invalid: other parameters after the parameter packtemplate<typename... Args, typename T> class B; 
  • Function parameter packs follow the same principle, usually placed at the end of the function parameter list (unless using special syntax like fold expressions).

2. Common Uses of Pack Expansion

In addition to recursive expansion, pack expansion can also be used for:

Passing parameters to other functions:

template<typename... Args>void wrapper(Args... args) {    other_func(args...); // Expand parameter pack, pass to other_func}

Generating type lists (e.g., <span>std::tuple</span><span><span> initialization):</span></span><pre><code class="language-plaintext">template<typename... Args>auto make_tuple(Args... args) { return std::tuple<Args...>(args...); // Expand both type pack and value pack}

3. Non-Type Variadic Templates (C++17+)

In addition to type parameter packs, non-type parameter packs are also supported (receiving non-type values such as integers, pointers, references, etc.):

// Non-type parameter pack: receives multiple integerstemplate<int... Nums>int sum() {    return (Nums + ...); // Fold expression (C++17+): sum all parameters}int main() {    std::cout << sum<1,2,3,4>(); // Output: 10    return 0;}

4. Simplifying to Fold Expression Variadic Templates

//c++11template<typename N>void display_arg(N n){    std::cout << n << std::endl;}template<typename... Args>void Display(Args... args){    int tmp[] = { (display_arg(args), 0)... };}//c++17//Unary right foldtemplate<typename... Args>void Display1(Args... args){    (display_arg(args), ...);}//Unary left foldtemplate<typename... Args>void Display2(Args... args){    (..., display_arg(args));}

5. Typical Application Scenarios of Variadic Templates

  1. Generic containers / data structures: such as std::tuple (storing values of any type), std::variant (C++17, type-safe union);

  2. Generic functions: such as std::printf (formatted printing), std::apply (C++17, calling functions that receive multiple parameters);

  3. Callback functions / event dispatching: receiving any number and any type of callback parameters;

  4. Metaprogramming: compile-time traversal of type lists, calculating the number of parameters, etc. (e.g., sizeof…(Args) calculates the length of the parameter pack).

6. Key Summary

  1. The core of variadic templates is parameter packs (<span><span>typename... Args</span></span>) and pack expansion (<span><span>Args...</span></span><code><span><span>);</span></span>
  2. Must be used in conjunction with “base case” + “recursive case” to expand parameter packs (can be simplified to fold expressions after C++17);
  3. Supports type parameter packs (any type) and non-type parameter packs (C++17+, any non-type value);
  4. It is a core technology for implementing “generic code,” with standard library components like <span><span>std::tuple</span></span> and <span><span>std::function</span></span> relying on it.

Leave a Comment