5.2.16 · D3 · Coding › C++ Programming › Variadic templates — parameter packs, fold expressions
Intuition Yeh page kya karta hai
Parent note ne parameter packs aur folds ke rules sikhaye the. Yahan hum har tarah ki situation dhundte hain jisme ek variadic template aa sakta hai — easy wale, empty-pack traps, direction-matters cases, aur exam twists — aur har ek ko end tak work out karte hain. Agar koi scenario exist karta hai, toh tum use yahan dekh chuke hoge.
Har variadic problem in cells mein se kisi ek mein aata hai. Hum sab cover karenge.
Cell
Kya cheez isse different banati hai
Danger agar ignore karo
A. Associative op, non-empty
+, *, && — fold ki direction value nahi badlti
kuch nahi, phir bhi ek fold pick karo
B. Non-associative op
-, /, string-build — left vs right alag answers dete hain
galat result silently
C. Empty pack, safe op
&&, ||, , ke identities hain
theek hai
D. Empty pack, unsafe op
+, * empty par → compile error
code build nahi hoga
E. Single-element pack
size 1 ka pack — "base of the fold"
off-by-one confusion
F. Mixed types
int, double, const char* saath mein
galat 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)
words → fold translate karna
J. Exam twist
fold-of-a-pattern, forwarding, index tricks
subtle expansion rules
Neeche: 9 worked examples, har ek cell(s) ke saath tagged.
Worked example Ek right fold se teen integers ka sum
template < typename ... Ts >
auto sum ( Ts ... xs ) { return (xs + ...); } // unary RIGHT fold
sum(4, 5, 6) compute karo.
Forecast: padhne se pehle number guess karo. (Yeh plain total hai.)
Fold expand karo. (xs + ...) jab ... right pe ho toh right se nest hota hai:
( 4 + ( 5 + 6 )) .
Yeh step kyun? Unary right fold (pack op ...) sabse gehri parentheses rightmost element pe lagata hai — yeh parent note ki definition hai.
Sabse andar wali pair pehle evaluate karo. 5 + 6 = 11 .
Yeh step kyun? Parentheses inner sum ko outer se pehle force karte hain.
Outer evaluate karo. 4 + 11 = 15 .
Yeh step kyun? Aur kuch nest nahi bachi — ek value bachi hai, jo fold ka result hai.
Verify: + associative hai, isliye left fold ((4+5)+6) = 15 same answer deta hai. Match ✓. Result ka type int hai (sab operands int). Answer: 15 .
Worked example Ek pack ko dono taraf se subtract karo
template < typename ... Ts > auto sub_right ( Ts ... xs ){ return (xs - ...); } // right
template < typename ... Ts > auto sub_left ( Ts ... xs ){ return (... - xs); } // left
sub_right(10, 3, 2) aur sub_left(10, 3, 2) compute karo.
Forecast: kya yeh equal honge? Pehle yes/no guess karo.
Right fold right se nesting expand karta hai: ( 10 − ( 3 − 2 )) .
Yeh step kyun? ... xs ke right mein hai, isliye rightmost element sabse gehre baithta hai.
Inner pehle: 3 − 2 = 1 , phir 10 − 1 = 9 .
Left fold left se nesting expand karta hai: (( 10 − 3 ) − 2 ) .
Yeh step kyun? ... xs ke left mein hai, isliye leftmost operation pehle bind hota hai.
Left pehle: 10 − 3 = 7 , phir 7 − 2 = 5 .
Verify: 9 = 5 — yeh equal nahi hain, jo confirm karta hai ki subtraction non-associative hai. Sanity check: normal "left-to-right subtraction" jo insaan karte hain woh 10 − 3 − 2 = 5 hai, jo left fold se match karta hai. Toh ordinary sequential subtraction ke liye, (... - xs) use karo. Answers: right = 9, left = 5 .
Worked example Zero arguments par
all_true aur any_true
template < typename ... Bs > bool all_true ( Bs ... bs ){ return (... && bs); }
template < typename ... Bs > bool any_true ( Bs ... bs ){ return (... || bs); }
all_true() aur any_true() kya return karte hain (koi argument nahi deke call karte hain)?
Forecast: "all-of-nothing" aur "any-of-nothing" "kya hona chahiye"?
Empty-pack unary fold identify karo. Zero elements ke saath, sirf &&, ||, aur , legal hain, aur har ek ki ek fixed identity value hai.
Yeh step kyun? Parent ka empty-pack rule: (... && args) = true, (... || args) = false jab pack empty ho.
all_true() → true. Kyun? "Conditions ke empty set mein sabhi conditions hold karti hain" vacuously true hai — koi false wali nahi hai jo isse tod sake. Yeh && identity se match karta hai.
any_true() → false. Kyun? "Kisi condition mein se koi bhi nahi hold karti" impossible hai — kuch bhi true hone ke liye nahi hai.
Verify: Boolean algebra: true && ki identity hai (x ∧ true = x ) aur false || ki identity hai (x ∨ false = x ). Identity ko empty answer ke roop mein use karna fold ko consistent rakhta hai jab tum ek aur element add karo. Answers: true aur false .
Worked example Ek pack ka sum jo shayad empty ho
template < typename ... Ts > auto bad_sum ( Ts ... xs ){ return (xs + ...); } // ❌ risky
template < typename ... Ts > auto safe_sum ( Ts ... xs ){ return ( 0 + ... + xs); } // ✅
bad_sum() vs safe_sum() ke saath kya hota hai (dono ko koi args nahi deke call karte hain)?
Forecast: kya bad_sum() 0 return karta hai, ya kuch aur bura?
bad_sum() ek empty pack par compile error hai.
Yeh step kyun? + ka koi defined empty-fold identity nahi — compiler arbitrary types ke liye "zero" invent nahi kar sakta. Sirf &&, ||, , exempt hain.
Fix: ek binary fold missing identity provide karta hai. (0 + ... + xs) ek binary left fold hai init = 0 ke saath.
Yeh step kyun? Jab xs empty ho toh poora expression sirf init, yaani 0 pe collapse ho jaata hai — koi illegal empty unary fold nahi hoti.
safe_sum() → 0. Kyun? Empty pack ⇒ result hai init = 0.
Non-empty phir bhi kaam karta hai: safe_sum(4, 5, 6) = (((0 + 4) + 5) + 6) = 15.
Kyun? Binary left fold init prepend karta hai, phir har element left→right fold karta hai.
Verify: safe_sum() = 0 (the init), safe_sum(4,5,6) = 15 (Example 1 se match karta hai). Parent ka rule of thumb: "empty packs se bachne ke liye init ke saath binary fold use karo." ✓
Worked example Exactly ek argument par fold
template < typename ... Ts > auto sub_right ( Ts ... xs ){ return (xs - ...); }
sub_right(7) compute karo — size one ka pack.
Forecast: sirf ek element ke saath, kya koi subtraction hogi?
Ek element par fold koi operation nahi karta. Expansion bas 7 hai.
Yeh step kyun? Fold operator pairs combine karta hai; single element ke saath koi pair nahi hoti, isliye akela element unchanged return ho jaata hai. - kabhi run nahi hota.
sizeof... se confirm karo. Andar, sizeof...(xs) == 1.
Yeh step kyun? Yeh reinforce karta hai ki hum sach mein ek element ke saath hain, zero nahi (jo Cell D hota).
Verify: sub_right(7) = 7 aur sub_left(7) = 7 — ek element ke liye direction irrelevant hai (associate karne ke liye kuch nahi hai). Answer: 7 .
int, ek double, aur ek char ka sum
template < typename ... Ts > auto sum ( Ts ... xs ){ return (xs + ...); }
sum(1, 2.5, 'A') compute karo aur result type batao.
Forecast: number aur type dono guess karo.
Right fold expand karo: (1 + (2.5 + 'A')).
Yeh step kyun? Right fold rightmost pehle nest karta hai, same as Example 1.
'A' arithmetic mein apne integer code 65 pe promote hota hai. Toh 2.5 + 'A' = 2.5 + 65 = 67.5.
Yeh step kyun? C++ mein, + mein char integral promotion se int ho jaata hai ('A' = 65 ASCII mein), phir double ke saath mix hota hai.
1 + 67.5 = 68.5. int double pe promote ho jaata hai kyunki ek operand double hai.
Yeh step kyun? C++ ki usual arithmetic conversions poore expression ko widest type pe promote karti hain — double.
Result type double hai (auto fold ke type se deduce karta hai).
Yeh step kyun? auto jo bhi fold expression evaluate karta hai use pick up karta hai.
Verify: 1 + 2.5 + 65 = 68.5 . Result 68.5 , type double . Types mix karna legal hai — compiler usual conversions se common type dhundta hai. ✓
Worked example Har element ko order mein print karo
template < typename ... Ts >
void print_each ( Ts ... xs ){ (( std ::cout << xs << ' ' ), ...); }
print_each(10, 20, 30) se kya print hota hai?
Forecast: exact printed string guess karo, spaces samet.
Comma fold pehchano. (expr, ...) ek unary right fold over the comma operator hai, jahan expr = (std::cout << xs << ' ').
Yeh step kyun? Hum ek side effect (printing) chahte hain har element pe, combined value nahi — comma operator har subexpression evaluate karta hai aur result discard karta hai.
Expansion: ((cout<<10<<' '), ((cout<<20<<' '), (cout<<30<<' '))).
Yeh step kyun? Right fold right se nest karta hai, lekin comma operator left-to-right evaluation guarantee karta hai , isliye 10 pehle print hota hai, 20 uske baad, 30 uske baad.
Har subexpression ek value aur ek space print karta hai , order mein.
Yeh step kyun? std::cout << xs << ' ' value likhta hai phir space; comma fold yeh har element ke liye repeat karta hai.
Verify: Output 9-character string "10 20 30 " hai (trailing space include hai). Left-to-right order guaranteed hai kyunki comma operator apne operands sequence karta hai — function-call arguments ki tarah nahi, jinका order unspecified hota hai. Answer: 10 20 30 .
Worked example Shopping cart ka average (word problem +
sizeof...)
Ek store item prices add karta hai aur item count se divide karta hai average paane ke liye. Ise likho, phir avg(2.0, 4.0, 9.0) par run karo.
template < typename ... Ts >
double avg ( Ts ... xs ){
return (xs + ...) / static_cast<double> ( sizeof... (xs));
}
Forecast: 2, 4, 9 ka average guess karo.
Fold se total. (xs + ...) = (2.0 + (4.0 + 9.0)) = 15.0.
Yeh step kyun? Right fold, associative + — value sum hai direction se regardless.
sizeof... se count. sizeof...(xs) = 3, ek constexpr size_t jo compile time par pata hoti hai.
Yeh step kyun? Hum kitne items hain se divide karna hai — sizeof... "kitne params hain" batata hai, sasti tarah se, bina recursion ke (contrast sizeof se, jo "kitne bytes" puchta hai).
Count ko divide karne se pehle double mein cast karo.
Yeh step kyun? double ko size_t se divide karna theek hai, lekin casting intent clear karti hai aur koi integer-division surprise avoid karta hai agar numerator integral hota.
Divide: 15.0/3 = 5.0 .
Verify: ( 2 + 4 + 9 ) /3 = 15/3 = 5.0 . Units: dollars ÷ (count) = dollars-per-item, ek valid average. Yeh ek runtime fold ko ek compile-time count ke saath mix karta hai — dekho 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) + ...); }
sum_sq(1, 2, 3) compute karo.
Forecast: value guess karo — aur ... se pehle pattern notice karo.
Repeated pattern identify karo. ... ke left mein jo hai woh (xs * xs) hai, isliye har element square hota hai, phir squares + se fold hote hain.
Yeh step kyun? Parent ka rule: "... ke left ka pattern har element ke liye repeat hota hai." Yahan pattern xs*xs hai, sirf xs nahi.
Expand: ((1*1) + ((2*2) + (3*3))) = (1 + (4 + 9)).
Yeh step kyun? Unary right fold right se nest karta hai har square compute hone ke baad.
Evaluate: 4 + 9 = 13 , phir 1 + 13 = 14 .
Verify: 1 2 + 2 2 + 3 2 = 1 + 4 + 9 = 14 . Yeh exam trap hai: students (xs + ...) * (xs + ...) likhte hain (galat — woh total ko square karta hai, 6 2 = 36 ). Correct sum-of-squares har element ko fold ke andar square karta hai. Answer: 14 .
Common mistake Teen traps jo is page ne drill kiye
Empty pack with +/* → compile error; binary fold init se fix karo (Ex 4).
Non-associative op (-, /) → left vs right differ karte hain; woh direction pick karo jo tumhari maths ko chahiye (Ex 2).
Sum ko square karna vs squares ka sum → pattern andar fold mein rakho (Ex 9).
Recall Close karne se pehle self-test
Ordinary human subtraction 10 - 3 - 2 kaun si fold direction deti hai? ::: Left fold (... - xs) = ((10-3)-2) = 5.
sum() empty pack ke saath aur (xs + ...) — kya hota hai? ::: Compile error (empty pack ke liye koi + identity nahi).
sum_sq(1,2,3) kya return karta hai aur 36 kyun nahi? ::: 14; pattern xs*xs har element ko square karta hai, total ko nahi.
avg(2.0,4.0,9.0) ka result? ::: 5.0 — sum 15 over count sizeof... = 3.
Dekho bhi: Function Templates , Recursion , std::tuple , Perfect Forwarding , Operator Overloading .