Intuition What this page does
The parent note taught the rules of parameter packs and folds. Here we hunt down every kind of situation a variadic template can land you in — the easy ones, the empty-pack traps, the direction-matters cases, and the exam twists — and work each one to the end. If a scenario exists, you will have seen it here.
Every variadic problem falls into one of these cells . We will cover all of them.
Cell
What makes it different
Danger if ignored
A. Associative op, non-empty
+, *, && — direction of fold doesn't change the value
none, but still pick a fold
B. Non-associative op
-, /, string-build — left vs right give different answers
wrong result silently
C. Empty pack, safe op
&&, ||, , have identities
fine
D. Empty pack, unsafe op
+, * over empty → compile error
code won't build
E. Single-element pack
pack of size 1 — the "base of the fold"
off-by-one confusion
F. Mixed types
int, double, const char* together
wrong common type
G. Side-effect / ordering
comma fold, print-each
evaluation order surprises
H. Compile-time count
sizeof..., constexpr
runtime vs compile-time mix-up
I. Word problem
real task (invoice total, logging)
translating words → fold
J. Exam twist
fold-of-a-pattern, forwarding, index tricks
subtle expansion rules
Below: 9 worked examples, each tagged with the cell(s) it covers.
Worked example Sum three integers with a right fold
template < typename ... Ts >
auto sum ( Ts ... xs ) { return (xs + ...); } // unary RIGHT fold
Compute sum(4, 5, 6).
Forecast: guess the number before reading. (It's the plain total.)
Expand the fold. (xs + ...) with the ... on the right nests from the right:
( 4 + ( 5 + 6 )) .
Why this step? A unary right fold (pack op ...) puts the deepest parentheses on the rightmost element — that's the definition from the parent note.
Evaluate the innermost pair first. 5 + 6 = 11 .
Why this step? Parentheses force the inner sum before the outer one.
Evaluate the outer. 4 + 11 = 15 .
Why this step? Nothing left to nest — one value remains, which is the fold's result.
Verify: + is associative, so a left fold ((4+5)+6) = 15 gives the same answer. Match ✓. Type of the result is int (all operands int). Answer: 15 .
Worked example Subtract a pack both ways
template < typename ... Ts > auto sub_right ( Ts ... xs ){ return (xs - ...); } // right
template < typename ... Ts > auto sub_left ( Ts ... xs ){ return (... - xs); } // left
Compute sub_right(10, 3, 2) and sub_left(10, 3, 2).
Forecast: will these be equal? Guess yes/no first.
Right fold expands nesting from the right: ( 10 − ( 3 − 2 )) .
Why this step? ... is to the right of xs, so the rightmost element sits deepest.
Inner first: 3 − 2 = 1 , then 10 − 1 = 9 .
Left fold expands nesting from the left: (( 10 − 3 ) − 2 ) .
Why this step? ... is to the left of xs, so the leftmost operation binds first.
Left first: 10 − 3 = 7 , then 7 − 2 = 5 .
Verify: 9 = 5 — they are not equal, confirming subtraction is non-associative. Sanity: normal "left-to-right subtraction" that humans do is 10 − 3 − 2 = 5 , which matches the left fold. So for ordinary sequential subtraction, use (... - xs). Answers: right = 9, left = 5 .
all_true and any_true on zero arguments
template < typename ... Bs > bool all_true ( Bs ... bs ){ return (... && bs); }
template < typename ... Bs > bool any_true ( Bs ... bs ){ return (... || bs); }
What do all_true() and any_true() return (called with no arguments)?
Forecast: what "should" all-of-nothing and any-of-nothing be?
Identify empty-pack unary fold. With zero elements, only &&, ||, and , are legal, and each has a fixed identity value.
Why this step? The parent's empty-pack rule: (... && args) = true, (... || args) = false when the pack is empty.
all_true() → true. Why? "All of an empty set of conditions hold" is vacuously true — there's no false one to break it. This matches the && identity.
any_true() → false. Why? "Some condition among none holds" is impossible — there's nothing to be true.
Verify: Boolean algebra: true is the identity of && (x ∧ true = x ) and false is the identity of || (x ∨ false = x ). Using the identity as the empty answer keeps the fold consistent when you add one more element. Answers: true and false .
Worked example Summing a pack that might be empty
template < typename ... Ts > auto bad_sum ( Ts ... xs ){ return (xs + ...); } // ❌ risky
template < typename ... Ts > auto safe_sum ( Ts ... xs ){ return ( 0 + ... + xs); } // ✅
What happens with bad_sum() vs safe_sum() (both called with no args)?
Forecast: does bad_sum() return 0, or something worse?
bad_sum() over an empty pack is a compile error.
Why this step? + has no defined empty-fold identity — the compiler cannot invent a "zero" for arbitrary types. Only &&, ||, , are exempt.
The fix: a binary fold supplies the missing identity. (0 + ... + xs) is a binary left fold with init = 0.
Why this step? When xs is empty the whole expression collapses to just init, i.e. 0 — no illegal empty unary fold occurs.
safe_sum() → 0. Why? Empty pack ⇒ result is init = 0.
Non-empty still works: safe_sum(4, 5, 6) = (((0 + 4) + 5) + 6) = 15.
Why? Binary left fold prepends init, then folds each element left→right.
Verify: safe_sum() = 0 (the init), safe_sum(4,5,6) = 15 (matches Example 1). Rule of thumb from parent: "use a binary fold with an init to survive empty packs." ✓
Worked example Fold over exactly one argument
template < typename ... Ts > auto sub_right ( Ts ... xs ){ return (xs - ...); }
Compute sub_right(7) — a pack of size one .
Forecast: with only one element, is there any subtraction at all?
A fold over one element does no operation. The expansion is just 7.
Why this step? The fold operator combines pairs ; with a single element there is no pair, so the lone element is returned unchanged. The - never runs.
Confirm with sizeof.... Inside, sizeof...(xs) == 1.
Why this step? Reinforces that we truly have one element, not zero (which would be Cell D).
Verify: sub_right(7) = 7 and sub_left(7) = 7 — direction is irrelevant for one element (there's nothing to associate). Answer: 7 .
int, a double, and a char
template < typename ... Ts > auto sum ( Ts ... xs ){ return (xs + ...); }
Compute sum(1, 2.5, 'A') and state the result type .
Forecast: guess the number and the type.
Expand the right fold: (1 + (2.5 + 'A')).
Why this step? Right fold nests rightmost first, same as Example 1.
'A' promotes to its integer code 65 in arithmetic. So 2.5 + 'A' = 2.5 + 65 = 67.5.
Why this step? In C++, a char in + undergoes integral promotion to int ('A' = 65 in ASCII), then mixes with double.
1 + 67.5 = 68.5. The int promotes to double because one operand is double.
Why this step? C++'s usual arithmetic conversions promote the whole expression to the widest type present — double.
Result type is double (deduced by auto from the fold's type).
Why this step? auto picks up whatever the fold expression evaluates to.
Verify: 1 + 2.5 + 65 = 68.5 . Result 68.5 , type double . Mixing types is legal — the compiler finds a common type via the usual conversions . ✓
Worked example Print each element in order
template < typename ... Ts >
void print_each ( Ts ... xs ){ (( std ::cout << xs << ' ' ), ...); }
What is printed by print_each(10, 20, 30)?
Forecast: guess the exact printed string, spaces included.
Recognise a comma fold. (expr, ...) is a unary right fold over the comma operator , where expr = (std::cout << xs << ' ').
Why this step? We want a side effect (printing) per element, not a combined value — the comma operator evaluates each subexpression and discards the result.
Expansion: ((cout<<10<<' '), ((cout<<20<<' '), (cout<<30<<' '))).
Why this step? Right fold nests from the right, but the comma operator guarantees left-to-right evaluation , so 10 prints before 20 before 30.
Each subexpression prints one value and a space , in order.
Why this step? std::cout << xs << ' ' writes the value then a space; the comma fold repeats this per element.
Verify: Output is the 9-character string "10 20 30 " (trailing space included). Left-to-right order is guaranteed because the comma operator sequences its operands — unlike function-call arguments, whose order is unspecified. Answer: 10 20 30 .
Worked example Average of a shopping cart (word problem +
sizeof...)
A store adds up item prices and divides by the item count to get an average. Write it, then run it on avg(2.0, 4.0, 9.0).
template < typename ... Ts >
double avg ( Ts ... xs ){
return (xs + ...) / static_cast<double> ( sizeof... (xs));
}
Forecast: guess the average of 2, 4, 9.
Total via fold. (xs + ...) = (2.0 + (4.0 + 9.0)) = 15.0.
Why this step? Right fold, associative + — value is the sum regardless of direction.
Count via sizeof.... sizeof...(xs) = 3, a constexpr size_t known at compile time .
Why this step? We must divide by how many items — sizeof... answers "how many params", cheaply, with no recursion (contrast with sizeof, which asks "how many bytes").
Cast the count to double before dividing.
Why this step? Dividing a double by a size_t is fine, but casting makes the intent explicit and avoids any integer-division surprise if the numerator were integral.
Divide: 15.0/3 = 5.0 .
Verify: ( 2 + 4 + 9 ) /3 = 15/3 = 5.0 . Units: dollars ÷ (count) = dollars-per-item, a valid average. This mixes a runtime fold with a compile-time count — see constexpr and Compile-time Computation . Answer: 5.0 .
Worked example Sum of squares (pattern expansion inside a fold)
template < typename ... Ts >
auto sum_sq ( Ts ... xs ){ return ((xs * xs) + ...); }
Compute sum_sq(1, 2, 3).
Forecast: guess the value — and notice the pattern before the ....
Identify the repeated pattern. The thing to the left of ... is (xs * xs), so each element gets squared, then the squares are folded with +.
Why this step? The parent rule: "the pattern to the left of ... is repeated for each element." Here the pattern is xs*xs, not just xs.
Expand: ((1*1) + ((2*2) + (3*3))) = (1 + (4 + 9)).
Why this step? Unary right fold nests from the right after each square is computed.
Evaluate: 4 + 9 = 13 , then 1 + 13 = 14 .
Verify: 1 2 + 2 2 + 3 2 = 1 + 4 + 9 = 14 . This is the exam trap: students write (xs + ...) * (xs + ...) (wrong — that squares the total , 6 2 = 36 ). The correct sum-of-squares squares each element inside the fold. Answer: 14 .
Common mistake The three traps this page drilled
Empty pack with +/* → compile error; fix with a binary fold init (Ex 4).
Non-associative op (-, /) → left vs right differ; pick the direction your maths needs (Ex 2).
Squaring the sum vs summing the squares → put the pattern inside the fold (Ex 9).
Recall Self-test before you close
Which fold direction gives ordinary human subtraction 10 - 3 - 2? ::: The left fold (... - xs) = ((10-3)-2) = 5.
sum() with an empty pack and (xs + ...) — what happens? ::: Compile error (no + identity for empty pack).
What does sum_sq(1,2,3) return and why not 36? ::: 14; the pattern xs*xs squares each element, not the total.
avg(2.0,4.0,9.0) result? ::: 5.0 — sum 15 over count sizeof... = 3.
See also: Function Templates , Recursion , std::tuple , Perfect Forwarding , Operator Overloading .