There's no sfinae here. If we look closely at the declaration of the function, and not it's implementation:
template <typename T>
class A
{
public:
void f();
};
You can see that there's nothing to tell the compiler that this function should not be part of overload resolution.
If we add a return type containing an expression, this is different:
template <typename T>
class A
{
public:
auto f() -> decltype(void(h(std::declval<T*>())));
};
That's different. Now The compiler need to resolve h(T*)
to perform overload resolution. If the substitution fails (the instantiation of the function declaration) then the function is not part of the set.
As for why the code still compiles, take a look at this:
void h(int *)
{
}
template <typename T>
class A
{};
template<typename T>
void f()
{
T *val;
h(val);
}
int main()
{
A<const int> a;
}
- It's undeniable that
f(A<const int>)
cannot work
- If you call
f(a)
, it fails to build as expected.
- Why is an instance of
A<const int>
even allowed to exist then?
The answer should become obvious now: the function is never instantiated!
The very same applies for member functions. There is no use to instantiate all member function if you don't use them.
If you look for an official statement that comfirm this behavior, then look no further than the standard itself.
From [temp.inst]/4 (emphasis mine):
Unless a member of a class template or a member template is a declared specialization, the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist or if the existence of the definition of the member affects the semantics of the program; in particular, the initialization (and any associated side effects) of a static data member does not occur unless the static data member is itself used in a way that requires the definition of the static data member to exist.