It was decided (by the C ++ committee) that the methods of template classes will have their own instances only if they are used.
This makes it easy to write C ++ code due to hard errors when they are used.
As an example, std::vector uses it with std::vector::operator< ; if you have no call < , this is a mistake. If you do, the call will work.
More modern C ++ will encourage SFINAE to disable it, so you can make sure that < is safe or not, but this method was not used when std::vector was developed. You can see the evolution of this technique in std::function , which came from hungry for almost anything in the universal constructor, so that this constructor was only considered to allow overloading when it worked between C ++ 11 and C ++ 14.
If you want SFINAE, you cannot rely on code bodies like this. To facilitate the loading of compilers, compilers should check for function definition declarations when running SFINAE tests.
Part of the reason is that SFINAE on expressions is complex; on whole bodies harder. The compiler must carefully compile the body of the function, click on the error, and then return to the "nope, nothing done" state.
Errors in the body of functions are always difficult errors. You cannot avoid this in the current version of C ++.
Now you can write functions that decide whether or not there will be errors, but there really is no error, and then use their bodies to determine if another code will be an error. For example:
template<class T> auto foo() { constexpr if(sizeof(T)<4) { return std::true_type{}; } else { return std::false_type{}; }
you can use foo<char>() in some SFINAE somewhere, and its true or false -ness can make another overload replacement or not.
Note that the error (if any) still occurs outside the function body ( foo here).