5.2.17 · D1C++ Programming

Foundations — SFINAE — substitution failure is not an error

2,108 words10 min readBack to topic

This page assumes you have seen none of the notation in the parent note. We build every piece — template, typename, T, ::, decltype, ..., enable_if, _v, _t — from the ground up, in an order where each idea only uses ideas already explained.


0. The stage: what is a function overload?

Before any templates, we need the word overload.

Picture a wall of labelled mailboxes, all named print, each accepting a different shape of letter. You drop a letter in; the post office decides which box it belongs to.

Figure — SFINAE — substitution failure is not an error

1. template <typename T> — a recipe with a blank

template <typename T>   // "for some type T ..."
T twice(T x) {          // ... here is a function that doubles x
    return x + x;
}
  • T is not a real type. It is a placeholder.
  • The real function does not exist until you call twice with something concrete.

You need templates because SFINAE is a rule about templates only. Non-template functions have no blanks to fill, so there is nothing to "substitute" and nothing to silently drop. See Templates and Type Deduction for how T gets guessed from the call.


2. Substitution & deduction — filling the blank

Two words that sound similar but are different steps.

Figure — SFINAE — substitution failure is not an error

3. The signature vs the body — WHERE the paste is watched

This is the single most important distinction on the whole topic.

Back to the résumé image from the parent: a broken header gets the résumé binned quietly (SFINAE); a fine header but nonsense spoken inside the interview (body) enrages the boss (hard error). The signature is the header; the body is the interview.


4. The :: operator — reaching inside a type

std::vector<int>::value_type   // the "value_type" that lives inside vector<int>  →  int
int::value_type                // ??? int has no member value_type  →  NONSENSE

This is why the parent's very first example (typename T::value_type) fails for T = int: after substitution the label tries to open a drawer that does not exist.


5. decltype(expr) — "ask the type of an expression without running it"

Why do we need a special tool for this, instead of just writing the type? Because with templates we don't know the type — it depends on the blank T. Only the compiler, after substitution, knows what t.size() returns. decltype lets us say "whatever that turns out to be."

Figure — SFINAE — substitution failure is not an error

See decltype and Trailing Return Types for how auto f(...) -> decltype(...) writes the return type after the parameters (so the parameter names like t are already in scope to be tested).


6. The comma operator inside decltype

The parent writes decltype(t.size(), size_t{}). Two things separated by a comma — what does that mean?

So decltype(t.size(), size_t{}):

  • checks that t.size() is a legal expression (validity gate → SFINAE),
  • but the resulting type is size_t{}'s type = size_t.

7. ... — the variadic "catch-all" parameter


8. std::enable_if, _t, _v — the switch

Now we assemble the previous pieces into the modern tool.

Combine with :: (Section 4): writing enable_if_t<false, R> tries to open the ::type drawer of a type that has no such drawer → nonsense in the label → SFINAE drops the overload.

Figure — SFINAE — substitution failure is not an error

See std::enable_if and Tag Dispatch for how this is used to split overloads, and Concepts (C++20) for the readable modern replacement of this whole machinery. The void_t Detection Idiom uses the same "make a drawer appear or vanish" trick with std::void_t.


9. How the pieces feed the topic

overload set - many same-named functions

overload resolution - pick the best

template T - a recipe with a blank

deduction - guess T

substitution - paste T into the label

signature vs body - WHERE nonsense counts

scope operator - reach inside a type

decltype - type of an expression

comma operator - validity gate

enable_if plus _t and _v - true or false to drawer exists

ellipsis - lowest priority fallback

SFINAE - bad label means quietly drop candidate


Equipment checklist

Cover the right side and test yourself. If any answer is fuzzy, re-read that section before the main note.

What is an overload set, and what step picks one member of it?
A family of functions sharing one name; overload resolution picks the single best match for your arguments.
What does template <typename T> create, and what is T?
A recipe with a blank; T is a placeholder for "some type we don't yet know."
Deduction vs substitution — which comes first and what does each do?
Deduction first (guess T from the call), then substitution (paste that T into the signature).
Where must nonsense appear for SFINAE (not a hard error) to apply?
In the signature/label — return type, parameter types, template-parameter defaults — never in the body.
What does A::B mean, and what happens if B doesn't exist in A?
"The member B living inside A"; if it's missing, the type is invalid — a substitution failure.
What does decltype(expr) give you, and does it run expr?
The type expr would produce; it does NOT run the expression.
In decltype(t.size(), size_t{}), what does the comma do?
Checks t.size() for validity (SFINAE gate) but makes the overall type size_t.
Why is a (...) parameter the ideal SFINAE fallback?
It is the lowest-priority match, so it only wins when all constrained overloads are removed.
What do the suffixes _t and _v mean?
_t = "the ::type member of ..."; _v = "the ::value (bool/number) of ...".
When does enable_if_t<Cond, R> become a substitution failure?
When Cond is false, because the ::type drawer does not exist, and naming a missing member is nonsense in the label.

Connections

  • ↑ Back to the main SFINAE note
  • Templates and Type Deduction
  • Overload Resolution
  • decltype and Trailing Return Types
  • Type Traits (std::is_integral, std::void_t)
  • std::enable_if and Tag Dispatch
  • void_t Detection Idiom
  • Concepts (C++20)