I have a basic grasp of SFINAE, and I think I understand many of the examples of how std::enable_if
exploits it to select function template specializations, but I'm having a hard time wrapping my head around how it works for class templates.
The following example is from cppreference.com's explanation of std::enable_if
:
template<class T, class Enable = void>
class A {}; // primary template
template<class T>
class A<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
}; // specialization for floating point types
I'm having trouble understanding how using std::enable_if
in this way helps select the specialization. (I don't doubt that it does.)
When the compiler sees a declaration like A<float> specialized;
, it will see two possible template instantiations that fit:
A<T, Enable>
where T
is the type float
and Enable is the type void
(because of the default value).A<T, void>
where T
is the type float
and void
is the result of the expression with the enable_if
.Aren't those ambiguous? Both effectively result in A<T, void>
, so why is the specialization chosen?
In a different case, like A<int> primary;
, the options to the compiler seem to be:
A<T, Enable>
, where T
is the type int
and Enable
is the type void
.A<T, ?>
, where T
is the type int
and the ?
represents where I'm completely lost. In this case, the enable_if
condition is false, so it doesn't define type
, which leaves you with A<int, typename >
. Isn't that a syntax error? Even in the face of SFINAE?From the reference on partial specialization of class templates:
When a class or variable (since C++14) template is instantiated, and there are partial specializations available, the compiler has to decide if the primary template is going to be used or one of its partial specializations.
If only one specialization matches the template arguments, that specialization is used
In this case, if the 2nd argument of the specialization is well-formed, it is chosen, precisely because it is a specialization, and not the primary template.
In case the 2nd template argument is not well-formed, then SFINAE kicks in. In particular:
When substituting the explicitly specified or deduced type for the template parameter fails, the specialization is discarded from the overload set instead of causing a compile error.
and
The following type errors are SFINAE errors:
attempting to use a member of a type, where the type does not contain the specified member
How this is done, i.e. how exactly the compiler discards the specialization, instead of giving an error, is not specified; the compiler is just required to do the right thing.