5.2.16 · Coding › C++ Programming
Kabhi kabhi aapko pehle se nahi pata hota ki ek function ya template ko kitne arguments milenge. printf koi bhi number of args leta hai, lekin wo unsafe hai (koi types nahi). Variadic templates aapko type-safe functions likhne dete hain jo any number of arguments of any types accept karti hain, aur compiler har call ke liye exact code generate karta hai.
YE KYUN EXIST KARTE HAIN: C++11 se pehle aapko ek function ko 1, 2, 3, ... N baar haath se overload karna padta tha. Wo repetitive tha aur limited bhi. Ek parameter pack "1 se N arguments" ko ek single reusable pattern mein fold kar deta hai.
Definition Parameter pack
Ek template parameter pack mein zero ya zyada template parameters hote hain. Ek function parameter pack mein zero ya zyada function arguments hote hain. Declare karne ke liye aur expand karne ke liye aap ... (ellipsis) likhte hain.
template<typename... Ts> void f(Ts... args)
typename... Ts — Ts ek pack of types hai.
Ts... args — args ek pack of values hai (function arguments).
sizeof...(Ts) ya sizeof...(args) — count (ek constexpr size_t), sizeof NAHI.
Teen jagah ... aata hai (KAISE padhen):
Position
Matlab
typename... Ts
ek type pack declare karo
Ts... args
ek value pack declare karo
args... (baad mein)
pack ko expand karo
Intuition "Left pe declare, right pe expand"
Jab ... naam se pehle ho, aap ek pack bana rahe ho. Jab ... pack wale kisi pattern ke baad ho, aap usse unpack kar rahe ho: args... → a0, a1, a2, aur f(args)... →
f(a0), f(a1), f(a2). ... ke left wala pattern har element ke liye repeat hota hai.
Fold expressions se pehle, aap ek pack ko head ko alag karke process karte the, tail pe recurse karte hue:
// Base case: koi arguments nahi bache
void print () { }
// Recursive case: 'first' ko alag karo, 'rest' pack pe recurse karo
template < typename T , typename ... Rest >
void print ( T first , Rest ... rest ) {
std ::cout << first << ' ' ;
print (rest...); // expand: baaki pack pe print call karo
}
print(1, "hi", 3.5) trace karna
T=1, rest={"hi",3.5} → 1 print karta hai, print("hi", 3.5) call karta hai
Kyun? Pehla arg first se bind hota hai; baaki sab rest mein collapse ho jaate hain.
T="hi", rest={3.5} → hi print karta hai, print(3.5) call karta hai
T=3.5, rest={} → 3.5 print karta hai, print() call karta hai
Ye step kyun? Empty pack base case print() se match karta hai, recursion rokta hai.
Output: 1 hi 3.5 . Compiler ne compile time par 4 alag functions generate kiye.
Base case kyun zaroori hai: recursion har call mein pack ko ek se chhhota karta hai. No-arg overload ke bina, aakhri print() call compile nahi hoti — recursion rokne ke liye kuch nahi hota.
Recursion verbose hai (ek idea ke liye do functions). Fold expressions ek pack ko ek binary operator se ek line mein collapse kar dete hain.
Ek pack args aur operator op ke liye:
Unary right: Unary left: Binary right: Binary left: ( a r g s o p ... ) ( ... o p a r g s ) ( a r g s o p ... o p ini t ) ( ini t o p ... o p a r g s ) = a 1 o p ( a 2 o p ( ... o p a n )) = (( a 1 o p a 2 ) o p ... ) o p a n = a 1 o p ( ... o p ( a n o p ini t )) = ((( ini t o p a 1 ) o p a 2 ) o p ... )
Rule of thumb: ... wahan baithta hai jahan pack "khulega". Pattern yeh hai: parentheses zaroori hain.
Intuition "Left" vs "right" kyun = association direction
Syntax padho: jis side ... hai, wo side sabse gehri nest hoti hai.
(... op args) → ... left pe hai, to leftmost ops pehle bind hote hain → left-assoc.
+ ke liye fark nahi padta, lekin -, /, ya strings banane mein padta hai!
template < typename ... Ts >
auto sum ( Ts ... xs ) {
return (xs + ...); // unary right fold: x1 + (x2 + (x3 + ...))
}
sum(1,2,3) ka expansion DERIVE karo:
( x s + ... ) → ( 1 + ( 2 + 3 )) = ( 1 + 5 ) = 6
Kyun? Unary right fold right se nest karta hai. + associative hai to value 6 hi rahegi.
all_true — && ke saath fold
template < typename ... Bs >
bool all_true ( Bs ... bs ) { return (... && bs); } // left unary fold
all_true(true, true, false) → ((true && true) && false) = false.
Left fold kyun? && left→right short-circuit karta hai; left fold wo natural order preserve karta hai.
Worked example Har arg pe function call karo (comma fold)
template < typename ... Ts >
void print_each ( Ts ... xs ) {
(( std ::cout << xs << ' ' ), ...); // comma fold: side-effects, in order
}
Comma operator kyun? (expr, ...) har pack element ke liye left→right expr evaluate karta hai aur results discard karta hai — printing jaise side effects ke liye perfect. Inner (...) ek "statement" wrap karta hai.
sizeof... se counting
template < typename ... Ts >
constexpr size_t count ( Ts ...) { return sizeof... (Ts); }
count(1, 'a', 3.0) → 3. Kyun? sizeof... elements ki number return karta hai, compile time pe evaluate hota hai — koi recursion nahi, koi fold nahi.
args likhna args... ki jagah
Galat sahi lagta hai kyunki: args ek normal variable jaisa dikhta hai. Lekin yeh ek pack hai — aap isse single value ki tarah use nahi kar sakte. Fix: jahan bhi pack use karo, ... se expand karo:
f(args...), (args + ...). Bare args sizeof... ke bahar compile error hai.
Common mistake Fold mein parentheses bhool jaana
return xs + ...; ❌ — fold expressions ko surrounding parentheses zaroori hain: return (xs + ...); ✅.
Rule kyun hai: parens fold ko ... ke doosre uses se alag karte hain.
sizeof... aur sizeof confuse karna
sizeof(Ts) ek pack pe illegal/meaningless hai; sizeof...(Ts) count deta hai. Dono jaisi lagte hain lekin ... sab badal deti hai: ek poochta hai "kitne bytes", doosra "kitne params".
Common mistake Possibly-empty pack pe unary fold
(xs + ...) compile error deta hai agar xs empty ho (+ ki koi identity nahi). Steel-man: lagta hai jaise 0 dena chahiye. Fix: binary fold with init use karo: (0 + ... + xs).
Common mistake Non-associative ops ke saath galat fold direction
sub(1,2,3) ke liye: (xs - ...) = 1-(2-3)=2, lekin (... - xs) = (1-2)-3=-4. Wo direction chuno jo aapka math chahta hai — dono equal NAHI hain.
typename... Ts kya declare karta hai?Ek template parameter pack — zero ya zyada types jinka naam Ts hai.
Pack Ts mein elements ki number kaise nikaalte hain? sizeof...(Ts) (ek constexpr size_t).
... ka matlab "pack declare karna" vs "pack expand karna" kahan hota hai?Naam se pehle = declare; pattern ke baad = expand.
xs = 1,2,3 ke liye (xs + ...) expand karo.1 + (2 + 3) = 6 (unary right fold, right se nest karta hai).
xs = 1,2,3 ke liye (... - xs) expand karo.(1 - 2) - 3 = -4 (unary left fold, left se nest karta hai).
Kaun se 3 operators empty pack pe unary fold allow karte hain, aur unki identities? &&→true, ||→false, ,→void().
Recursive pack processing mein base-case overload kyun chahiye? Pack empty hone par recursion rokne ke liye.
Ek pack jo empty ho sakta hai, usse safely sum kaise karein? Binary fold with init: (0 + ... + xs).
Pack args ke har element pe f call karne ka syntax kya hai? (f(args), ...); ya kisi call ke andar f(args)....
Fold expression mein parentheses kyun zaroori hain? Fold ko ... ke doosre uses se alag karne ke liye; yeh syntax rule hai.
Recall Feynman: ek 12-saal ke bacche ko samjhao
Ek magic gift-wrapping machine imagine karo. Aapko pehle se nahi pata ki aap use 1 toy doge ya 10, lekin wo sab ko wrap kar leti hai chahe kitni bhi hon. Ek parameter pack "toys ka dhera" hai, aur ... aapka yeh kehna hai "dheri mein har toy ke liye yahi karo". Ek fold toys ko stack karne jaisa hai: (x + ...) matlab "sabko ek dabbe mein daalo", right se shuru karo aur unhe milate jao jab tak sirf ek daabba (answer) na bache. Computer program chalane se pehle hi exact wrapping steps bana leta hai — to yeh flexible bhi hai AUR safe bhi.
Mnemonic Folds yaad karne ka tarika
"Dots deep end ki taraf point karte hain." Jis side ... hai, wo side sabse gehri nest hoti hai.
(... op xs) → dots left pe → left -associative. (xs op ...) → dots right pe → right .
Aur "Pack? Pack the dots!" — har pack use mein kahin na kahin ellipsis zaroori hai.
Templates and Generics — variadic templates Function Templates ka general case hain.
std::tuple — parameter packs par bana hai; std::make_tuple variadic hai.
Perfect Forwarding — template<typename... Ts> f(Ts&&... a){ g(std::forward<Ts>(a)...); }.
Recursion — pre-C++17 pack processing type list pe structural recursion hai.
constexpr and Compile-time Computation — sizeof... aur folds compile time pe evaluate hote hain.
Operator Overloading — folds kisi bhi binary operator ke saath kaam karte hain, overloaded wale bhi.
Base case stops recursion