Exercises — SFINAE — substitution failure is not an error
5.2.17 · D4· Coding › C++ Programming › SFINAE — substitution failure is not an error
Vocabulary ka ek chhota sa reminder, taaki koi bhi symbol anjana na lage:
Related tools jinhe aap use kar sakte ho: Templates and Type Deduction, std::enable_if and Tag Dispatch, decltype and Trailing Return Types, Type Traits (std::is_integral, std::void_t), void_t Detection Idiom, Overload Resolution, Concepts (C++20).
Level 1 — Recognition
L1.1 — Signature hai ya body?
Har snippet ke liye batao ki failure SFINAE hai (silently candidate remove karta hai) ya HARD error hai (compilation rok deta hai).
// (a)
template <typename T>
typename T::value_type a(const T&); // called with T = int
// (b)
template <typename T>
void b(const T&) { typename T::value_type x; } // called with T = intRecall Solution
- (a) Illegal cheez
int::value_typereturn type mein hai = signature. → SFINAE. Candidateasilently drop ho jaata hai. Agar yahi ek candidate tha to aapko "no matching function" milega. - (b) Signature
void b(const T&)T = intke liye bilkul valid hai. Illegalint::value_typebody ke andar hai. Body immediate context nahi hai → HARD error. Ek-line rule: toota hua header = résumé phenk do; toota hua andar = interview crash ho jaata hai.
L1.2 — ::type kya hai?
Inki value (ya non-existence) batao:
std::enable_if<true, double>::type aur std::enable_if<false, double>::type.
Recall Solution
enable_if<true, double>::type=double(truepartial specializationusing type = T;define karti hai).enable_if<false, double>::type= exist nahi karta. Primary template mein koi::typenahi hai, isliye ise name karna substitution failure hai → overload drop ho jaata hai.
Level 2 — Application
L2.1 — Output predict karo
#include <cstdio>
template <typename T>
auto f(const T& t) -> decltype(t.size(), int{}) { return 1; } // A
template <typename T>
int f(...) { return 2; } // B
int main() {
std::printf("%d %d\n", f(std::string("hi")), f(42));
}Yeh kya print karta hai, aur har number kyun?
Recall Solution
Print karta hai 1 2.
f(std::string("hi")):std::stringmein.size()hai, isliyedecltype(t.size(), int{})valid hai → A survive karta hai aur B ke variadic...se better match hai → A jeet jaata hai → 1.f(42):intmein koi.size()nahi, isliye A ka return type substitution failure hai → A drop ho jaata hai → sirf B bacha (variadic, worst match, hamesha valid) → B jeet jaata hai → 2.decltypeke andar comma operator:t.size()sirf validity ke liye evaluate hoti hai, phir discard ho jaati hai; pooradecltypeint{}'s type, yaaniint, yield karta hai.
L2.2 — Overlap fix karo
template <typename T>
std::enable_if_t<std::is_integral_v<T>, void> g(T); // A
template <typename T>
std::enable_if_t<sizeof(T) >= 1, void> g(T); // Bg(5) call karo. Kya galat hota hai aur kaise fix karoge?
Recall Solution
T = int ke liye: condition A true hai (int integral hai) aur condition B sizeof(int) >= 1 bhi true hai. Dono overloads survive karte hain → ambiguous call → hard error, "compiler ek choose karta hai" nahi.
Fix: conditions ko ek clean partition banao. B ki condition ko A ke negation se replace karo:
template <typename T>
std::enable_if_t<!std::is_integral_v<T>, void> g(T); // B fixedAb har T ke liye A / B mein se exactly ek viable hoga.
Level 3 — Analysis
L3.1 — Return-type enable_if twins par kyun break hota hai?
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
void h(T);
template <typename T, typename = std::enable_if_t<!std::is_integral_v<T>>>
void h(T);Yeh dono alag lagte hain (opposite conditions). Lekin compiler inhe "redefinition / cannot be overloaded" ke saath reject kar sakta hai. Explain karo kyun, aur safe idiom do.
Recall Solution
Kyun break hota hai: ek default template argument overloads distinguish karne ke purpose ke liye function ki signature ka hissa nahi hota. Dono templates same signature template<class T, class> void h(T) pe reduce ho jaate hain. Identical signatures wale do templates = same template ki redeclaration → error, SFINAE run hone se pehle.
Safe idiom — enable_if ko ek non-type template parameter mein daalo jo substitution mein participate kare:
template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
void h(T);
template <typename T, std::enable_if_t<!std::is_integral_v<T>, int> = 0>
void h(T);Yahan std::enable_if_t<cond,int> ek defaulted non-type parameter ka type hai. Jab cond false hoti hai, woh type exist nahi karti → SFINAE cleanly overload drop kar deta hai, aur dono alag rehte hain.
L3.2 — Drop trace karo
Parent note se getSize ke liye:
template <typename T>
auto getSize(const T& t) -> decltype(t.size(), size_t{}) { return t.size(); } // A
template <typename T> size_t getSize(...) { return 0; } // BgetSize(3.14) (ek double) ke liye exact substitution steps list karo.
Recall Solution
- Deduce
T = doubleconst T& tsedoubleargument match karke. - A ke return type mein Substitute karo:
decltype( (3.14).size(), size_t{} ). Sub-expressiont.size()kodouble::size()chahiye —doublemein koi membersizenahi → substitution failure in the immediate context (return type). - SFINAE: A silently overload set se remove ho jaata hai.
- B mein parameter
...hai (hamesha viable, worst rank) aur koi constraint nahi → B akela survivor hai → choose ho jaata hai. getSize(3.14)0return karta hai. Koi hard error nahi, "double has no size" ka koi message nahi.
Level 4 — Synthesis
L4.1 — void_t se has_begin detector banao
void_t Detection Idiom use karke ek trait has_begin<T> likho jo true ho jab T mein member begin() ho aur false otherwise. Phir har moving part explain karo.
Recall Solution
#include <type_traits>
// Primary: assume NO begin. Second param defaulted to void.
template <typename T, typename = void>
struct has_begin : std::false_type {};
// Specialization: only viable when decltype(...) is a valid type -> void.
template <typename T>
struct has_begin<T, std::void_t<decltype(std::declval<T&>().begin())>>
: std::true_type {};Moving parts:
std::void_t<...>kisi bhi valid type list kovoidmein map karta hai. Iska poora kaam ek "kya yeh expression compile hua?" switch hona hai.decltype(std::declval<T&>().begin()):declval<T&>()ekT&value bina construct kiye conjure karta hai (hume sirf unevaluateddecltypemein iska type chahiye). AgarTmeinbeginnahi hai, to yeh substitution failure hai — lekin yeh specialization ke template argument mein hai, isliye SFINAE specialization ko sirf non-viable bana deta hai.- Jab
.begin()valid hai → doosra argumentvoidhai, jo primary ke default= voidse match karta hai, isliye specialization (more specialized) choose hoti hai →true_typeinherit karti hai. - Jab
.begin()invalid hai → specialization drop out ho jaati hai → primary (false_type) use hoti hai. Usage:has_begin<std::vector<int>>::valuetruehai;has_begin<int>::valuefalsehai.
L4.2 — Same idea ek Concept (C++20) se
L4.1 detector ke intent ko ek concept use karke rewrite karo aur ek function constrain karo.
Recall Solution
#include <concepts>
template <typename T>
concept HasBegin = requires (T& t) { t.begin(); }; // does t.begin() compile?
template <HasBegin T> void use(T& t) { /* iterate */ }
template <typename T> void use(T&) { /* fallback */ }requires(T& t){ t.begin(); } block exactly wahi SFINAE test hai jo readable bana diya gaya hai: yeh true hai iff t.begin() ek well-formed expression hai. Concepts wahi removal from the overload set karte hain jo SFINAE karta hai — lekin constraint named hai aur error messages cryptic no-match ki jagah HasBegin point karte hain.
Level 5 — Mastery
L5.1 — Clean partition ke saath three-way dispatch
describe(T) ke teen overloads likho:
- integral types →
1return karta hai - floating-point types →
2return karta hai - baaki sab →
3return karta hai
enable_if use karke, kisi bhi T ke liye koi ambiguity guarantee nahi. Phir describe(7), describe(2.0), describe("hi") predict karo.
Recall Solution
#include <type_traits>
template <typename T,
std::enable_if_t<std::is_integral_v<T>, int> = 0>
int describe(T) { return 1; }
template <typename T,
std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
int describe(T) { return 2; }
template <typename T,
std::enable_if_t<!std::is_integral_v<T> && !std::is_floating_point_v<T>, int> = 0>
int describe(T) { return 3; }Teeno conditions ek partition hain: integral, floating, aur "na integral na floating." Kisi bhi T ke liye exactly ek true hoga, isliye exactly ek overload survive karega — koi ambiguity nahi.
describe(7)→7inthai (integral) → 1.describe(2.0)→2.0doublehai (floating) → 2.describe("hi")→ literal"hi"const char[3]hai, na integral na floating → 3. (Neeche partition diagram dekho — har type exactly ek region mein aata hai.)

L5.2 — Crash debug karo
Ek student ek "detector" likhta hai jo hamesha hard-crash karta hai:
template <typename T>
struct is_addable {
static constexpr bool value = requires { std::declval<T>() + std::declval<T>(); };
T probe = std::declval<T>() + std::declval<T>(); // <-- line X
};Exact line identify karo jo T = int ke liye bhi hard error banati hai, aur detector fix karo.
Recall Solution
Line X bug hai: T probe = std::declval<T>() + std::declval<T>(); ek evaluated member initializer struct body ke andar hai. Do problems:
std::declval<T>()sirf unevaluated contexts (decltype,sizeof,requires,noexcept) mein appear ho sakta hai — ise real initializer mein use karna harTke liye ill-formed hai,intke liye bhi.- Yah compile bhi ho jaata, tab bhi initializer body mein hai, isliye non-addable
Thard error deta, cleanfalsenahi. Fix — probe ko unevaluated rakho, member hataao:
template <typename T>
struct is_addable {
static constexpr bool value =
requires (T a, T b) { a + b; }; // pure compile-time test, no body probe
};Ab is_addable<int>::value == true, is_addable<std::mutex>::value == false, aur dono taraf koi hard error nahi.
Recall Self-quiz ke liye one-line summaries
SFINAE ke liye failure kahan rehni chahiye? ::: Signature mein (immediate context) — kabhi body mein nahi.
Do equally-viable SFINAE overloads → ? ::: Ambiguous call = hard error.
Twin overloads ko distinct kaise banate hain? ::: std::enable_if_t<cond,int> = 0 as a non-type param, default ke roop mein nahi.
std::void_t<decltype(expr)> kya test karta hai? ::: Kya expr ek well-formed type/expression hai.
std::declval<T>() kahan appear ho sakta hai? ::: Sirf unevaluated contexts mein (decltype/sizeof/noexcept/requires).
Connections
- Parent: SFINAE
- Templates and Type Deduction
- std::enable_if and Tag Dispatch
- decltype and Trailing Return Types
- Type Traits (std::is_integral, std::void_t)
- void_t Detection Idiom
- Overload Resolution
- Concepts (C++20)