5.2.15 · D3 · Coding › C++ Programming › Template specialization — full and partial
Intuition Yeh page kya hai
Parent note ne tumhe rules bataye the. Yahan hum specialization problem ke har shape ko drill karenge — har match, har miss, har tie — taaki koi bhi exam ya codebase tumhe surprise na kar sake. Socho jaise chessboard ke har square pe chalna taaki pata chale uspe kya hai.
Yeh page assume karta hai ki tum basic template syntax se pehle mil chuke ho. Agar template<class T> abhi bhi alien lagta hai, pehle woh padho.
Kisi bhi example se pehle, do words ko pakka karte hain jo hum baar baar use karte hain.
Definition Pattern aur shape gate
Pattern — woh type expression jo ek specialization ke angle brackets mein likhi hoti hai, jaise struct IsPtr<T*> mein T*. Yeh ek stencil hai: compiler tumhare actual type X pe ise rakhke poochta hai "kya main placeholders (T, A, N, …) fill in kar sakta hoon taaki stencil exactly X ban jaye?" Agar haan, toh pattern X ko match karta hai.
Shape gate — us yes/no test ka ek informal naam. T* jaisa pattern ek gate hai jo sirf pointer types ke liye khulta hai, kyunki sirf ek pointer ko "koi T, aur phir ek *" ke roop mein likha ja sakta hai. int T* gate se nahi guzar sakta; int* guzar sakta hai (T = int ke saath).
Toh "matching" = "kya pattern ka stencil X pe fit hota hai?" aur "shape gate" = wohi idea, ek darwaze ki tarah picture karo jo sirf certain type-shapes ke liye fit hota hai.
Neeche ki figure darwaze ka metaphor literally draw karti hai — ise dimag mein rakho, kyunki is page pe har example bas yahi hai: "kaunse gates se X guzrta hai?"
Teen darwaze dekho. Har ek ek pattern ki shape mein kata hua hai: sabse wide opening T hai (primary — koi bhi type fit hoti hai), medium wali T* hai (sirf pointer-shaped types fit hoti hain), aur sabse narrow int* hai (sirf woh exact type fit hoti hai). Do travellers dekho: yellow int block pointer-shaped nahi hai, toh woh sirf sabse wide T door se guzar sakta hai. Pink int* block pointer-shaped hai, toh woh teeno doors se fit hota hai — aur compiler hamesha ise sabse narrow door se bhejta hai jisme woh fit hota hai, jo hai int*. Yahi "narrowest door wins" hai — ek picture mein poora selection rule.
Do words — pattern aur shape gate — yaad rakho: neeche ka har example bas yahi hai: "kaunse patterns' gates se X guzrta hai, aur kaunsa survivor sabse specialized hai?"
Har specialization question aslmein yahi hai: "type X ke liye kaunsa candidate jitega? " Candidates hain: primary (generic fallback), ek full spec (saare parameters concrete types pe pin), aur ek ya zyada partial specs (shape constrained, kuch parameters abhi free). Tum jo type X feed karte ho woh input hai; winning candidate output hai.
Neeche ka map har case class ko shape gates ke landscape ki tarah lay out karta hai, na ki text ki wall ki tarah. Ise ek decision fan ki tarah padho: ek input X left se enter karta hai aur exactly ek cell mein land karta hai depending on kitne gates woh fit karta hai aur woh gates kaisa relate karte hain.
Figure mein har labelled node matrix ki ek cell hai — ek alag scenario jo topic tumpe throw kar sakta hai. Color outcome batata hai (white = primary fires, blue = partial fires, yellow = full spec fires, pink = error). Har node ke neeche chota tag (Ex 1, Ex 2, …) us worked example ki taraf point karta hai jo use drill karta hai. Cells hain:
A — koi spec match nahi → primary fires (fallback). (Ex 1)
B — ek full spec match karta hai → woh sab kuch beat karta hai, kyunki X exactly woh pinned type hai. (Ex 1, Ex 8)
C — ek partial match karta hai aur primary bhi match karti hai → partial jitati hai. (Ex 2)
D — ek parameter pinned , doosra abhi free (mixed concrete + free). (Ex 3)
E — do candidates compete karte hain , ek strictly zyada specialized hai → woh subset ordering se jitata hai. (Ex 4)
F — do partials tie karte hain , na to ek doosre ka subset hai → ambiguity error (the degenerate case). (Ex 5)
G — nested shape (T**, pointer-to-pointer) → most-nested pattern jitata hai. (Ex 6)
H — zero / degenerate input jaise void aur void* (ek boundary type). (Ex 7)
I — ek real-world word problem : ek per-type serializer. (Ex 8)
J — exam twist : ek function ko partially specialize karne ki koshish → uske bajaye overloading use karo. (Ex 9)
K — limiting behaviour : reference vs pointer vs array shapes sab alag hain, har ek apna partial fire karta hai. (Ex 10)
Neeche har worked example us cell(s) ke saath tagged hai jo woh cover karta hai. Milke woh poora map fill karte hain.
Figure ko top se bottom padhdo: ek type X drop hoti hai (top, pale-yellow box), compiler saare candidates collect karta hai (white box), un sab ko bahar phenkta hai jinke pattern shape gate fail karte hain X ke liye (pink box — "unhe discard karo jinke pattern X se match nahi karte"), phir survivors mein se most specialized choose karta hai (blue box). Neeche ke teen outcome boxes — primary / partial / full — wohi teen colors hain jo tum har example mein dekhoge: fallback ke liye white, partial ke liye blue, full spec ke liye yellow.
Worked example Example 1 — Cell A + B: primary vs full, do extremes
template < class T > struct Printer { static const char* fmt (){ return "value" ; } };
template <> struct Printer < bool > { static const char* fmt (){ return "true/false" ; } };
Printer < double >:: fmt (); // ?
Printer < bool >:: fmt (); // ?
Forecast: Aage padhne se pehle dono outputs guess karo. Kaunsi type ke paas custom recipe hai?
double ke liye candidates list karo. Sirf primary; full spec bool pe pin hai, aur double ≠ bool.
Yeh step kyun? List-then-filter hamesha pehla move hota hai: matching (shape gate) pehla gate hai, toh ek candidate jo X se match nahi karta woh kisi bhi "specificity" contest shuru hone se pehle discard ho jaata hai. Koi compare karne se pehle hume pata hona chahiye ki kaun abhi bhi khada hai.
double ke liye koi spec match nahi karta → primary fires. Toh Printer<double>::fmt() returns "value". (Cell A.)
Yeh step kyun? Jab sirf fallback bachta hai, toh iske saath compare karne ke liye kuch nahi hota — winner default se decide ho jaata hai.
bool ke liye candidates list karo. Primary match karti hai (koi bhi T) aur full spec Printer<bool> exactly match karti hai.
Yeh step kyun? Same reason as step 1 — hum filter nayi input ke liye re-run karte hain, kyunki ek alag X alag gates se guzar sakta hai.
Full beats primary. Ek full spec sabse specific cheez hai — zero free parameters. Toh Printer<bool>::fmt() returns "true/false". (Cell B.)
Yeh step kyun? "Tumne exact type naam li" — yeh intent ka sabse strong signal hai.
Verify: Do distinct types, do distinct outputs — caller ek naam Printer use karta hai aur type-appropriate behaviour milta hai. double ke liye "value", bool ke liye "true/false". ✓
Worked example Example 2 — Cell C: partial
T* beats primary
template < class T > struct IsPtr { static const bool value = false ; };
template < class T > struct IsPtr < T * > { static const bool value = true ; };
IsPtr < int >::value; // ?
IsPtr < int* >::value; // ?
Forecast: Kaunsa wala true aata hai?
int feed karo. Primary match karti hai (T = int). Kya IsPtr<T*> match karta hai? Use X ka pointer hona zaruri hai; int nahi hai. Partial discard karo.
Yeh step kyun? Hum hamesha pehle har pattern ke shape gate ko X ke against run karke shuru karte hain. Pattern T* ek gate hai jo sirf pointer types ke liye khulta hai, toh int isse bounce ho jaata hai.
Sirf primary bachti hai → false. Toh IsPtr<int>::value == false. (Contrast ke liye Cell A firse.)
int* feed karo. Primary match karti hai (T = int*). Partial T* bhi T = int ke saath match karta hai. Do survivors.
Partial ordering. T* (pointers) se match hone wali har cheez T (sabhi types) se match hone wali cheez ka strict subset hai. Toh T* more specialized hai → woh jitata hai.
Yeh step kyun? Subset = "tumhara clearly narrower case matalab tha."
Result: IsPtr<int*>::value == true. (Cell C.)
Neeche ki figure exactly yeh subset relationship draw karti hai — iss par rukke raho, kyunki is page par hone wala har "partial beats primary" decision yahi picture hai.
Do circles dekho: bada white circle hai "saare types jo T se match karte hain" (primary). Inner blue circle hai "pointer types jo T* se match karte hain" (partial). Pink dot int* dono circles ke andar baithta hai, toh dono patterns match karte hain — lekin blue circle puri tarah white circle ke andar hai, jo exactly yeh hai ki "T* T se zyada specialized hai." Yellow dot int sirf white circle mein baithta hai, toh sirf primary fire karti hai iske liye.
Verify: Yeh literally waise hi build hota hai jaise std::is_pointer — is_pointer<int>::value hai false, is_pointer<int*>::value hai true. Humara miniature STL trait ko exactly reproduce karta hai. ✓
Worked example Example 3 — Cell D: ek parameter pin karo, doosra free rakho
template < class A , class B > struct Pair2 { static const char* k (){ return "AB" ; } };
template < class B > struct Pair2 < int , B > { static const char* k (){ return "intB" ; } };
Pair2 < double , char >:: k (); // ?
Pair2 < int , char >:: k (); // ?
Pair2 < int , int >:: k (); // ?
Forecast: Teen calls — teeno predict karo.
Pair2<double,char>: gate run karo. Partial ka pattern hai <int, B>, jo demand karta hai ki pehla argument exactly int ho; yahan woh double hai, toh gate band rehta hai. Partial discard karo → primary → "AB". (Cell A.)
Yeh step kyun? Hamesha ranking se pehle filter karo — ek pattern jo match karne mein fail hota hai specificity contest mein enter hi nahi kar sakta.
Pair2<int,char>: partial B = char ke saath match karta hai, primary bhi match karti hai. Partial ek slot pin karta hai (ek subset) → more specialized → jitata hai → "intB". (Cell D.)
Yeh step kyun? Ek bhi parameter ko concrete type pe pin karna family ko narrow karta hai, aur narrower wins.
Pair2<int,int>: yahan A=int aur B=int. Partial abhi bhi sirf pehle slot ko pin karta hai; doosra slot B free hai aur khushi se int se bind ho jaata hai. Sirf ek partial exist karta hai, toh koi competition nahi → "intB". (Cell D.)
Yeh step kyun? Ek free parameter ka int se bind hona ek doosre pinned parameter jaisa nahi hai — yahan sirf ek partial hai, toh koi ambiguity nahi.
Verify: Outputs hain "AB", "intB", "intB". Pehla slot int hona trigger hai; doosre slot ki value isse irrelevant hai ki kaunsa candidate fire karta hai. ✓
Worked example Example 4 — Cell E: do candidates, ek strictly more specialized
template < class T > struct S { static const char* w (){ return "generic" ; } };
template < class T > struct S < T * > { static const char* w (){ return "ptr" ; } };
template <> struct S < int* > { static const char* w (){ return "int-ptr" ; } };
S < double* >:: w (); // ?
S < int* >:: w (); // ?
Forecast: int* ke liye, teen candidates match karte hain. Kaun jitega?
S<double*>: har candidate pe gate run karo. Primary match karti hai; S<T*> T=double ke saath match karta hai; full S<int*> nahi karta (double ≠ int). Do survivors: primary aur T*. T* more specialized hai → "ptr". (Cell C.)
Yeh step kyun? Hum pehle filter karte hain taaki pata chale ki full spec double* ke liye running se bahar hai — warna hum galti se "int-ptr" expect kar sakte the.
S<int*>: teeno match karte hain! Primary (koi bhi T), partial T* (T=int ke saath), full S<int*> (exact).
Yeh step kyun? Nayi input ke liye gate re-run karne se pata chalta hai ki is baar teen candidates survive karte hain, toh tie-break ladder actually matter karta hai.
Full beats partial beats primary. Full spec ke zero free parameters hain — maximally specific → woh jitata hai → "int-ptr". (Cell E, ordering se resolve hota hai.)
Yeh step kyun? Yeh poora ordering ladder dikhata hai: full ⟵ more constrained partial ⟵ less constrained partial ⟵ primary.
Verify: double* ke liye "ptr", int* ke liye "int-ptr". Full spec ek matching partial ko bhi cleanly override karta hai. ✓
Worked example Example 5 — Cell F (degenerate): do partials TIE karte hain → error
template < class A , class B > struct M { };
template < class A > struct M < A , int > { }; // <X, int> match karta hai
template < class B > struct M < int , B > { }; // <int, X> match karta hai
M <int , int> m; // ?
Forecast: Kya yeh compile hoga?
M<int,int> ke liye candidates: har gate run karo. Primary match karti hai; M<A,int> match karta hai (A=int); M<int,B> match karta hai (B=int). Do partials plus primary survive karte hain.
Yeh step kyun? Ordering check karne se pehle hume survivors enumerate karne chahiye, kyunki ambiguity surviving set ki property hai, kisi single candidate ki nahi.
Do partials compare karo. Kya "doosra arg int hai" "pehla arg int hai" ka subset hai? Nahi — M<char,int> pehle se match karta hai lekin doosre se nahi. Kya reverse subset hai? Woh bhi nahi — M<int,char> doosre se match karta hai lekin pehle se nahi.
Yeh step kyun? Kisi bhi direction mein subset nahi = koi bhi doosre se zyada specialized nahi.
Rule 5 fires: ambiguity error. Compiler guess karne se mana karta hai. (Cell F — the degenerate case.)
Yeh step kyun? Chupchap guess karna ek footgun hoga; hard error tumhe disambiguate karne ke liye force karta hai.
Verify: Yeh compile nahi hota — yahan assertion yeh hai ki "compilation fails with an ambiguity diagnostic," na ki koi runtime value. Fix: ek teesra full spec template<> struct M<int,int>{}; add karo jo dono partials ko beat karta hai. ✓ (Koi numeric check nahi — outcome compile error hai.)
Worked example Example 6 — Cell G: nested shapes, most-nested jitata hai
template < class T > struct N { static int id (){ return 0 ; } }; // koi bhi T
template < class T > struct N < T * > { static int id (){ return 1 ; } }; // koi bhi pointer
template < class T > struct N < T ** > { static int id (){ return 2 ; } }; // pointer-to-pointer
N < int >:: id (); // ?
N < int* >:: id (); // ?
N < int** >:: id (); // ?
Forecast: Teeno inputs ko rank karo ki woh kaunsa id produce karte hain.
int: gates run karo — na T* na T** non-pointer ke liye khulta hai, toh sirf primary survive karta hai → 0.
Yeh step kyun? Pehle filter karne se immediately pata chalta hai ki dono pointer patterns ek plain scalar ke liye irrelevant hain, toh koi ranking needed nahi — fallback default se jitata hai.
int*: primary match karti hai (T = int*) aur T* match karta hai (T = int). Kya T** match karta hai? Use pointer-to-pointer chahiye; int* ek pointer-to-int hai, pointer-to-pointer nahi, toh T** gate band rehta hai. T** discard karo. Primary aur T* ke beech, partial T* strict subset hai → woh jitata hai → 1.
Yeh step kyun? Hume deepest gate bhi check karna hai aur confirm karna hai ki woh fail hota hai, warna hum galti se 2 expect kar sakte the; tabhi hum survivors ko rank kar sakte hain.
int**: teeno match karte hain. Primary (T = int**); T* match karta hai kyunki int** ek pointer hai (int* ko), toh T = int*; aur T** T = int ke saath match karta hai. Teen survivors.
Yeh step kyun? Deepest input ke liye har gate re-run karna candidates ka poora stack unrank karne se pehle dikhata hai.
T** strict subset hai T* ka , jo strict subset hai T ka (har pointer-to-pointer ek pointer hai, aur har pointer ek type hai, kabhi reverse nahi). Toh T** most specialized hai → woh jitata hai → 2. (Cell G.)
Yeh step kyun? Deeper nesting = stronger constraint = specialization ladder pe upar, toh most-nested pattern hamesha jitata hai jab woh fit hota hai.
Verify: ids hain 0, 1, 2 — ek clean staircase jaise pointer nesting deeper hoti hai. ✓
Worked example Example 7 — Cell H: boundary type
void aur void*
template < class T > struct V { static const char* w (){ return "generic" ; } };
template < class T > struct V < T * > { static const char* w (){ return "ptr" ; } };
V < void >:: w (); // ?
V < void* >:: w (); // ?
Forecast: void ek aisi type hai jiska tum kabhi object nahi bana sakte. Kya partial phir bhi void* dekhta hai?
V<void>: gate run karo. void pointer nahi hai, toh T* match nahi kar sakta (koi T nahi hai jiske liye T* = void). Sirf primary → "generic". (Cell H, degenerate input.)
Yeh step kyun? void jaisi ek "weird" boundary type bhi wohi shape-gate test follow karti hai jaise koi ordinary type — woh simply pointer pattern fail karti hai, aur hume kuch bhi assume karne se pehle confirm karna hai.
V<void*>: void* ek pointer hai , T = void ke saath T* match karta hai. Yeh fact ki tum void* dereference nahi kar sakte pattern matching ke liye irrelevant hai — sirf shape matter karta hai. Partial jitata hai → "ptr". (Cell H, limiting.)
Yeh step kyun? Specialization type structure se match karta hai, type ki "usability" se nahi.
Verify: void ke liye "generic", void* ke liye "ptr". void boundary exactly kisi bhi non-pointer ki tarah behave karta hai; void* exactly kisi bhi pointer ki tarah behave karta hai. Koi special-casing needed nahi. ✓
Worked example Example 8 — Cell I + B: real-world word problem, ek per-type serializer
Ek logging library ko kisi bhi value ko string tag mein convert karna hai. Numbers "num" dete hain, C-strings "cstr" dete hain, aur koi bhi pointer "ptr:" deta hai jiske baad pointee ka tag aata hai. Ise design karo aur int, const char*, aur double* ke tags predict karo.
template < class T > struct Tag { static string of (){ return "num" ; } };
template <> struct Tag < const char* > { static string of (){ return "cstr" ; } };
template < class T > struct Tag < T * > { static string of (){ return "ptr:" + Tag < T >:: of (); } };
Forecast: Tag<double*>::of() kya return karta hai? (Dhyan rakho — yeh recurse karta hai.)
Tag<int>: gates run karo — koi pointer shape nahi, aur yeh const char* bhi nahi → primary → "num".
Yeh step kyun? Pehle filter karna confirm karta hai ki koi special candidate apply nahi hota, toh generic "num" sahi hai na ki koi accident.
Tag<const char*>: full spec <const char*> aur partial T* (with T=const char) dono match karte hain. Full beats partial → "cstr". (Real design ke andar Cell B.)
Yeh step kyun? Hum chahte the ki C-strings ko text ki tarah treat kiya jaye, na ki "pointer to char" ki tarah, toh full spec partial ko outrank karna chahiye — aur karta bhi hai, automatically.
Tag<double*>: T = double ke saath T* se match karta hai; full spec apply nahi hoti. Partial fires → "ptr:" + Tag<double>::of() → "ptr:" + "num" → "ptr:num". (Cell I, partial ke through recursion ke saath.)
Yeh step kyun? Partial Tag<T> ko pointee ke liye callback karta hai, toh specialization ek choti type-level program ki tarah compose karta hai.
Verify: Tag<int>::of() == "num", Tag<const char*>::of() == "cstr", Tag<double*>::of() == "ptr:num". Full spec const char* ke liye cleanly jitata hai, ise pointer ki tarah mis-tag hone se rokta hai. ✓ Related design tools: tag dispatch .
Worked example Example 9 — Cell J: exam trap — "ek function ko partially specialize karo"
template < class T > void f ( T ) { /* generic */ }
// ATTEMPT (illegal) — function template ki partial specialization:
// template<class T> void f<T*>(T*) { /* pointer version */ }
// ^^^^^ <-- compiler: "function template partial
// specialization is not allowed"
// FIX — iske bajaye ek doosra OVERLOAD likho (naam ke baad koi <...> nahi):
template < class T > void f ( T * ) { /* pointer overload */ }
Forecast: Commented-out line kyun fail hoti hai lekin last line succeed karti hai, jabki woh almost identical lagte hain?
Illegal line ki syntax padho. f<T*>(T*) function naam ke baad angle brackets mein ek pattern rakhta hai — yeh ek partial specialization ki syntax hai. Classes ke liye yeh legal hai; functions ke liye nahi.
Yeh step kyun? Tumhe pehle identify karna chahiye ki code kaun sa language feature use karne ki koshish kar raha hai, kyunki error pointers ke baare mein bilkul nahi hai — yeh f<...> form functions par forbidden hone ke baare mein hai.
Rule yaad karo. Function templates sirf FULL specialization support karte hain, PARTIAL kabhi nahi. Toh template<> void f<int*>(int*){} (ek full spec, empty <>) legal hoga, lekin template<class T> void f<T*>(T*){} (ek partial spec, T abhi bhi free) ill-formed hai aur compiler ise reject karta hai.
Yeh step kyun? Exact rule ko naam dena ek mysterious error message ko ek predictable banata hai — exam sirf yahi ek fact test kar raha hai.
Samjho kyun yeh rule exist karta hai. Functions ke paas already overload resolution hai, jo candidate functions ko rank karta hai. Partial specialization add karna ek doosra , competing ranking system create karta hai, isliye committee ne functions se partial specs bahar rakhi. Classes ke paas koi overloading nahi, toh unhe partial specialization ki zarurat hai.
Yeh step kyun? Reason jaanne ka matlab hai ki tum kabhi syntax tweak karke error "fix" karne ki koshish nahi karoge — mechanism itself galat tool hai.
Fix apply karo. Last line, template<class T> void f(T*), ke f ke baad koi <...> nahi hai, toh yeh koi specialization nahi hai — yeh ek brand-new overload hai. f(somePtr) jaisi call ke liye, overload resolution f(T*) ko f(T) se zyada specialized match pata hai aur ise pick karta hai, exactly woh pointer-specific behaviour deta hai jo hum chahte the.
Yeh step kyun? Yeh behaviour partial spec ka us mechanism ke through deliver karta hai jo functions actually support karte hain.
Verify: Commented f<T*> form compile error hai ("function template partial specialization is not allowed"); overload form compile hoti hai aur pointer arguments ko f(T*) pe dispatch karti hai. Assertion: functions → sirf full spec ya overload; partial function spec ill-formed hai. ✓ (Outcome compile-time rule hai, numeric value nahi.)
Worked example Example 10 — Cell K: limiting shapes — reference vs pointer vs array shapes sab alag hain
template < class T > struct Kind { static int id (){ return 0 ; } }; // scalar/other
template < class T > struct Kind < T * > { static int id (){ return 1 ; } }; // pointer
template < class T > struct Kind < T & > { static int id (){ return 2 ; } }; // lvalue reference
template < class T , int N > struct Kind < T [ N ]>{ static int id (){ return 3 ; } }; // fixed array
Kind < int >:: id (); // ?
Kind < int* >:: id (); // ?
Kind < int& >:: id (); // ?
Kind < int [ 4 ]>:: id (); // ?
Forecast: Char alag type "shapes" — aage padhne se pehle charon ids predict karo.
int: har gate run karo. Yeh pointer nahi, reference nahi, array nahi, toh teen shape patterns mein se koi nahi khulta → primary → 0.
Yeh step kyun? Pehle filter karna prove karta hai ki plain scalar har special gate se guzar jaata hai aur fallback pe land karta hai — kuch exotic nahi hota.
int*: sirf T* gate khulta hai (T=int ke saath); reference aur array gates band rehte hain. Primary ke alawa ek survivor, aur woh more specialized hai → 1.
Yeh step kyun? Har shape pattern alag structure pe gate karta hai, toh hum unhe ek ek check karte hain aur sirf woh rakhte hain jo khulta hai.
int&: sirf T& gate khulta hai → 2. Reference ek distinct shape hai; yeh pointer nahi hai, toh T* gate iske liye nahi khulta.
Yeh step kyun? Yeh ek common muddle hai ki "reference ≈ pointer"; gates prove karte hain ki woh alag shapes hain alag patterns ke saath.
int[4]: array gate T[N] T=int aur N=4 ke saath khulta hai → 3. Note karo ki partial ek non-type parameter N (ek integer) introduce karta hai, ek pattern ko array sizes ki poori family match karne deta hai.
Yeh step kyun? Partial specs ऐसी structure pin kar sakte hain jo compile-time constants jaise array length shamil kare, toh int[4], int[10], … sab same partial fire karte hain.
Verify: ids hain 0, 1, 2, 3 — char disjoint shapes, char distinct answers, zero overlap aur zero ambiguity. Yeh "spanning" case hai: har partial type-space ka apna corner cover karta hai, toh har input ka exactly ek clear winner hota hai. ✓
Mnemonic Ek rule jo upar ke har example ko resolve karta hai
F.P.P. — Full > Partial > Primary , aur partials mein, narrower wins . Agar do equally narrow hain → ambiguity . Is page pe har ek example ek alag shape ke X par apply hone wala wahi rule hai.
Recall
int** ke liye, rank karo ki T, T*, T** mein se kaun jitega aur kyun
Teeno match karte hain; T** strict subset hai T* ka, jo strict subset hai T ka. ::: T** jitata hai — woh most specialized hai (deepest nesting = strongest constraint).
Recall Kya
V<void*> T* partial fire karta hai? Kyun ya kyun nahi?
Specialization type structure se match karta hai, usability se nahi. ::: Haan — void* ka pointer shape hai (T* with T=void), toh partial fire hota hai chahe tum void* dereference nahi kar sakte.
Recall
Tag<const char*> ko "ptr:cstr" nahi balki "cstr" kyun tag kiya jaata hai?
Kyunki full specialization ek matching partial ko outrank karti hai. ::: Full spec Tag<const char*> partial Tag<T*> ko beat karta hai, toh woh kabhi pointer branch mein recurse nahi karta.
Recall Ex 5 mein ambiguity error aata hai. Ise fix karne wali ek single line kya hai?
Dono partials se strictly more specialized kuch add karo. ::: template<> struct M<int,int>{}; — ek full spec dono partials ko beat karta hai, tie todta hai.
Recall
int[4] aur int& ke liye array aur reference partials ke beech kabhi ambiguity kyun nahi hoti?
Unke shape gates disjoint hain. ::: Reference array nahi hai aur array reference nahi hai, toh har input exactly ek gate kholta hai — sirf ek partial kabhi survive karta hai.
Related deep tools worth a visit: SFINAE and enable_if , constexpr if , aur container quirk std::vector<bool> special case .