5.2.16C++ Programming

Variadic templates — parameter packs, fold expressions

2,073 words9 min readdifficulty · medium

1. Parameter packs — WHAT they are

Three places the ... appears (HOW to read it):

Position Meaning
typename... Ts declare a type pack
Ts... args declare a value pack
args... (after) expand the pack

2. Recursive expansion — the pre-C++17 way (DERIVE it)

Before fold expressions, you processed a pack by peeling off the head, recursing on the tail:

// Base case: no arguments left
void print() { }
 
// Recursive case: peel 'first', recurse on the 'rest' pack
template<typename T, typename... Rest>
void print(T first, Rest... rest) {
    std::cout << first << ' ';
    print(rest...);          // expand: call print on the remaining pack
}

WHY a base case is needed: recursion shrinks the pack by one each call. Without the no-arg overload, the final print() call wouldn't compile — there'd be nothing to stop the recursion.


3. Fold expressions — the C++17 way (the 80/20 win)

Recursion is verbose (two functions for one idea). Fold expressions collapse a pack with a binary operator in one line.

template<typename... Ts>
auto sum(Ts... xs) {
    return (xs + ...);        // unary right fold: x1 + (x2 + (x3 + ...))
}

DERIVE the expansion of sum(1,2,3): (xs+...)    (1+(2+3))  =  (1+5)  =  6(xs + ...) \;\to\; (1 + (2 + 3)) \;=\; (1 + 5) \;=\; 6 Why? Unary right fold nests from the right. + is associative so the value is 6 either way.

Figure — Variadic templates — parameter packs, fold expressions

4. Worked examples


5. Common mistakes


Flashcards

What does typename... Ts declare?
A template parameter pack — zero or more types named Ts.
How do you get the number of elements in a pack Ts?
sizeof...(Ts) (a constexpr size_t).
Where does ... mean "declare a pack" vs "expand a pack"?
Before the name = declare; after a pattern = expand.
Expand (xs + ...) for xs = 1,2,3.
1 + (2 + 3) = 6 (unary right fold, nests from right).
Expand (... - xs) for xs = 1,2,3.
(1 - 2) - 3 = -4 (unary left fold, nests from left).
Which 3 operators allow a unary fold over an empty pack, and their identities?
&&→true, ||→false, ,→void().
Why does recursive pack processing need a base-case overload?
To stop recursion when the pack becomes empty.
How do you safely sum a pack that might be empty?
Binary fold with init: (0 + ... + xs).
What's the syntax to call f on every element of pack args?
(f(args), ...); or f(args)... inside a call.
Why are parentheses mandatory in a fold expression?
To distinguish a fold from other ... uses; it's a syntax rule.

Recall Feynman: explain to a 12-year-old

Imagine a magic gift-wrapping machine. You don't know if you'll hand it 1 toy or 10 toys, but it wraps all of them no matter how many. A parameter pack is the "pile of toys", and ... is you saying "do this for every toy in the pile". A fold is like stacking the toys: (x + ...) means "add them all into one box", starting from the right and squishing them together until just one box (the answer) is left. The computer builds the exact wrapping steps before the program even runs — so it's both flexible AND safe.

Connections

  • Templates and Generics — variadic templates are the general case of Function Templates.
  • std::tuple — built on parameter packs; std::make_tuple is variadic.
  • Perfect Forwardingtemplate<typename... Ts> f(Ts&&... a){ g(std::forward<Ts>(a)...); }.
  • Recursion — pre-C++17 pack processing is structural recursion on the type list.
  • constexpr and Compile-time Computationsizeof... and folds are evaluated at compile time.
  • Operator Overloading — folds work with any binary operator, including overloaded ones.

Concept Map

motivates

type-safe alt to

built on

declared with

before name

after pattern

counted by

processed pre-C++17

needs

processed C++17

collapses with

replaces

Unknown arg count

Variadic templates

printf unsafe

Parameter packs

Ellipsis dots

Declare pack

Expand pack

sizeof... count

Recursive expansion

Base case stops recursion

Fold expressions

Binary operator

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho, kabhi-kabhi humein aisa function chahiye jo kitne bhi arguments le sake — jaise printf. Lekin printf type-safe nahi hai. Variadic templates is problem ko solve karte hain: template<typename... Ts> likho aur ab Ts ek parameter pack ban gaya — yaani "ek dher of types". Ts... args matlab values ka dher. Jab tum ... ko naam ke aage lagaate ho to pack declare hota hai, aur peeche lagaate ho (jaise args...) to pack expand hota hai. Count chahiye? sizeof...(Ts) use karo — ye sizeof se alag hai, ye batata hai kitne elements hain.

Purane C++ me pack ko process karne ke liye recursion karni padti thi: ek argument peel karo (first), baaki ko rest... me daalo, aur dobara call karo — saath me ek empty base-case function zaroori hota tha warna recursion kabhi rukti nahi. C++17 me aaye fold expressions jo same kaam ek line me kar dete hain: (xs + ...) saare elements ko + se jod deta hai. Parentheses lagana mandatory hai, warna compile error.

Sabse important point: left fold vs right fold. (... op xs) left fold hai (left se nest hota hai), (xs op ...) right fold hai (right se). + ke liye fark nahi padta, par -, / ya string banane me direction se result badal jaata hai — diagram me dekho 1-2-3 ka answer 2 ya -4 ho sakta hai! Aur ek trap: agar pack khaali ho sakta hai, to unary fold (jaise (xs + ...)) compile error dega — tab binary fold init ke saath use karo: (0 + ... + xs). Sirf &&, ||, aur , operators empty pack ke saath chalte hain (true, false, void).

Go deeper — visual, from zero

Test yourself — C++ Programming

Connections