5.2.16 · D4 · HinglishC++ Programming

ExercisesVariadic templates — parameter packs, fold expressions

2,639 words12 min read↑ Read in English

5.2.16 · D4 · Coding › C++ Programming › Variadic templates — parameter packs, fold expressions

Shuru karne se pehle, ek picture vo vocabulary pin down karti hai jo hum har problem mein reuse karte hain.

Figure — Variadic templates — parameter packs, fold expressions

Level 1 — Recognition

L1.1 — Ellipsis padhna

Har line ke liye batao ki ... ek pack declare karta hai ya expand karta hai:

  1. template<typename... Ts>
  2. void f(Ts... args)
  3. g(args...)
  4. (args + ...)
Recall Solution

Hamari reading key: ... naam ke pehle declare karta hai; ... ek pattern ke baad expand karta hai.

  1. typename... Ts... Ts se pehle hai → declare ek type pack.
  2. Ts... args... args se pehle hai → declare ek value pack.
  3. args...... pattern args ke baad aata hai → expand (produce karta hai a0, a1, a2, ...).
  4. (args + ...)... fold pattern ke baad aata hai → expand ek fold ke roop mein.

To: declare, declare, expand, expand.

L1.2 — Count vs size

f(1, 'a', 3.14, "hi") ke liye, sizeof...(Ts) kya return karta hai, aur yeh kyun nahi hai sizeof(Ts)?

Recall Solution

4 arguments hain, isliye sizeof...(Ts) . Yeh count karta hai ki pack mein kitne template parameters hain — compile time par known ek constexpr size_t. sizeof(Ts) illegal hai: Ts types ki poori ek pile hai, ek type nahi, isliye "kitne bytes hai yeh" ka koi jawab nahi hai. ... sawaal ko bytes se count mein badal deta hai.


Level 2 — Application

L2.1 — Ek right fold evaluate karo

sum(Ts... xs){ return (xs + ...); } diya hua, sum(10, 20, 30, 40) compute karo aur nesting likho.

Recall Solution

Unary right fold right se nest karta hai: Answer: 100. (Kyunki + associative hai, value ek left fold se match karti, lekin shape right-nested hai.)

L2.2 — Ek non-associative op ke saath ek left fold evaluate karo

sub(1, 2, 3, 4) compute karo jahan sub left fold (... - xs) use karta hai.

Recall Solution

Unary left fold left se nest karta hai (sabse leftmost op pehle bind karta hai): Answer: -8.

L2.3 — Side effects ke liye comma fold

((std::cout << xs << ' '), ...); print_each('C', '+', '+') ke liye kya print karta hai?

Recall Solution

Comma fold apna pattern har element ke liye left to right ek baar evaluate karta hai, har result discard karta hai. To yeh cout << 'C' << ' ' run karta hai, phir cout << '+' << ' ', phir cout << '+' << ' '. Output (note trailing space): C + + . Hum comma operator precisely isliye use karte hain kyunki hum order mein side effect (printing) ki parwah karte hain, return values combine karne ki nahi.


Level 3 — Analysis

L3.1 — Empty pack

In mein se kaun compile hote hain jab pack xs empty ho, aur kya value result hoti hai?

  1. (xs + ...)
  2. (xs && ...)
  3. (xs || ...)
  4. (xs, ...)
  5. (0 + ... + xs)
Recall Solution

Sirf teen unary folds ke paas ek empty pack ke liye ek defined identity hai; baaki compile errors hain.

  1. (xs + ...)compile error (unary fold mein + ke liye koi identity element nahi).
  2. (xs && ...)compile hota hai, value true.
  3. (xs || ...)compile hota hai, value false.
  4. (xs, ...)compile hota hai, value void() (kuch nahi).
  5. (0 + ... + xs)compile hota hai, value 0. Yeh ek binary fold hai jiska init = 0 missing identity supply karta hai, isliye yeh kisi bhi size ke liye safe hai, zero bhi.

Lesson: ek possibly-empty pack ko safely sum karne ke liye, hamesha ek init do: (0 + ... + xs).

L3.2 — Association shape predict karo

concat ke liye jo + ke saath string-join karta hai, kya (xs + ...) vs (... + xs) result badalta hai? xs = "a","b","c" (std::string ke roop mein) diya hua, dono outputs do.

Recall Solution

String + produce kiye gaye characters ke liye associative hai, isliye dono "abc" dete hain.

  • Right fold (xs + ...): "a" + ("b" + "c") = "a" + "bc" = "abc".
  • Left fold (... + xs): ("a" + "b") + "c" = "ab" + "c" = "abc". Value identical hai; sirf intermediate temporaries differ karte hain. Isko subtraction se contrast karo jahan value khud badal jaati hai — isliye "kya direction matter karta hai?" operator ki algebra par depend karta hai.

L3.3 — Count karo kitne booleans true hain

count_true(Bs... bs) likho aur trace karo jo count karta hai kitne arguments true hain, ek fold use karke. count_true(true, false, true, true, false) ke liye evaluate karo.

Recall Solution

Har bool 0 ya 1 mein convert hota hai, isliye ek sum fold trues count karta hai:

template<typename... Bs>
constexpr int count_true(Bs... bs) {
    return (0 + ... + static_cast<int>(bs));   // binary left fold, safe if empty
}

Trace: . Answer: 3. Humne binary fold init = 0 ke saath choose kiya taaki empty case (count_true() = 0) abhi bhi compile ho.


Level 4 — Synthesis

L4.1 — min_of scratch se build karo

Ek variadic min_of(a, rest...) likho jo sabse chhota argument return kare. min_of(5, 2, 8, 1, 9) trace karo.

Recall Solution

Ek plain operator ke saath ek fold seedha min nahi karega, lekin hum ek lambda-like helper ke saath fold kar sakte hain, ya recurse kar sakte hain. Sabse clean fold ek comma-driven update ke zariye std::min use karta hai, lekin sabse clear synthesis ek helper ke zariye ternary use karke ek running minimum par fold hai. Yahan ek recursive build hai (parent ke peel-the-head pattern se match karta hai):

template<typename T> T min_of(T a) { return a; }              // base: one left
template<typename T, typename... R>
T min_of(T a, R... rest) {
    T m = min_of(rest...);        // smallest of the tail
    return a < m ? a : m;         // compare head against it
}

Trace min_of(5,2,8,1,9):

  • min_of(9) = 9
  • min_of(1,9) → m=9 → 1<9 → 1
  • min_of(8,1,9) → m=1 → 8<1? no → 1
  • min_of(2,8,1,9) → m=1 → 2<1? no → 1
  • min_of(5,2,8,1,9) → m=1 → 5<1? no → 1

Answer: 1. Har call ek head peel karta hai aur rukne ke liye base case min_of(T a) ki zaroorat hoti hai.

L4.2 — Ek fold jo count bhi kare

average(xs...) likho jo arithmetic mean double ke roop mein return kare. average(2, 4, 9) ke liye evaluate karo.

Recall Solution

Ek sum fold ko sizeof... ke saath combine karo:

template<typename... Ts>
double average(Ts... xs) {
    return (0.0 + ... + xs) / sizeof...(xs);   // safe even if... (see note)
}

Trace average(2,4,9): sum ; count ; mean . Answer: 5.0. (Real code mein divide karne se pehle sizeof...(xs) == 0 ke against guard karo.)

L4.3 — Ek pack ko ek tuple mein forward karo

Dikhao ki sab arguments ko ek std::tuple mein value categories preserve karte hue kaise store karein, Perfect Forwarding use karke. pack_it(1, "x", 2.5) ke result ka std::tuple_size kya hai?

Recall Solution
template<typename... Ts>
auto pack_it(Ts&&... xs) {
    return std::make_tuple(std::forward<Ts>(xs)...);  // expand: forward each element
}

Pattern std::forward<Ts>(xs) trailing ... dwara har element ke liye ek baar repeat hota hai, isliye har argument apni lvalue/rvalue nature rakhta hai. pack_it(1, "x", 2.5) 3 elements ka tuple build karta hai, isliye std::tuple_size<decltype(...)>::value = 3sizeof...(Ts) se match karta hai.


Level 5 — Mastery

L5.1 — Custom operator ke saath fold

operator| overloaded diya hua taaki a | b return kare a agar a>b else b (ek "max" pipe), to (x | ...) (3, 7, 2, 9, 5) ke liye kya compute karta hai? Dekho Operator Overloading.

Recall Solution

Right fold right se nest karta hai, lekin | yahan bada return karta hai, aur "bada" associative hai, isliye order value nahi badalta: Answer: 9. Isi tarah tum ek variadic max ek fold line mein paate ho jab ek baar operator comparison encode kar le.

L5.2 — Compile-time fold

constexpr and Compile-time Computation use karke, ek constexpr variadic product likho aur product(2, 3, 5) evaluate karo — aur explain karo ki multiplication kab hoti hai.

Recall Solution
template<typename... Ts>
constexpr auto product(Ts... xs) { return (1 * ... * xs); }  // binary left fold, init=1

. Answer: 30. Kyunki function constexpr hai aur arguments literals hain, compiler poore fold ko compile time par evaluate karta hai — runtime binary mein sirf constant 30 hota hai. init = 1 bhi product() (empty) ko safely 1 return karta hai.

L5.3 — Mixed fold, exact output predict karo

In ka printed output, order mein, trace karo:

template<typename... Ts>
void dump(Ts... xs) {
    std::size_t i = 0;
    ((std::cout << i++ << ":" << xs << ' '), ...);
}

dump("a", 42, 'Z') ke liye.

Recall Solution

Comma fold pattern left→right evaluate karta hai, aur i++ har print ke baad advance karta hai:

  • element "a": print karta hai 0:a , phir i 1 ho jaata hai
  • element 42: print karta hai 1:42 , phir i 2 ho jaata hai
  • element 'Z': print karta hai 2:Z , phir i 3 ho jaata hai

Output: 0:a 1:42 2:Z . Comma fold ka Left-to-right evaluation guaranteed hai, isliye index counter consistent rehta hai.


Recall Self-test summary

4 args ke liye sizeof...(Ts) ::: 4 sum(10,20,30,40) (xs + ...) se ::: 100 sub(1,2,3,4) (... - xs) se ::: -8 count_true(T,F,T,T,F) ::: 3 min_of(5,2,8,1,9) ::: 1 average(2,4,9) ::: 5.0 product(2,3,5) (1 * ... * xs) se ::: 30 Empty-pack (xs + ...) ::: compile error; (0 + ... + xs) use karo