What exactly is the “immediate context” mentioned in the C++11 Standard for which SFINAE applies?

If you consider all the templates and implicitly-defined functions that are needed to determine the result of the template argument substitution, and imagine they are generated first, before substitution starts, then any errors occurring in that first step are not in the immediate context, and result in hard errors.

If all those instantiations and implicitly-definitions (which might include defining functions as deleted) can be done without error, then any further “errors” that occur during substitution (i.e. while referring to the instantiated templates and implicitly-defined functions in the function template’s signature) are not errors, but result in deduction failures.

So given a function template like this:

template<typename T>
void
func(typename T::type* arg);

and a “fall-back” that will be used if deduction fails for the other function:

template<typename>
void
func(...);

and a class template like this:

template<typename T>
  struct A
  {
    typedef T* type;
  };

A call to func<A<int&>>(nullptr) will substitute A<int&> for T and in order to check if T::type exists it must instantiate A<int&>. If we imagine putting an explicit instantiation before the call to func<A<int&>(nullptr):

template class A<int&>;

then that would fail, because it tries to create the type int&* and pointers to references are not allowed. We don’t get to the point of checking if substitution succeeds, because there is a hard error from instantiating A<int&>.

Now let’s say there’s an explicit specialization of A:

template<>
  struct A<char>
  {
  };

A call to func<A<char>>(nullptr) requires the instantiation of A<char>, so imagine an explicit instantiation somewhere in the program before the call:

template class A<char>;

This instantiation is OK, there’s no error from this, so we proceed to argument substitution. The instantiation of A<char> worked, but A<char>::type doesn’t exist, but that’s OK because it’s only referenced in the declaration of func, so only causes argument deduction to fail, and the fall-back ... function gets called instead.

In other situations substitution might cause special member functions to be implicitly-defined, possibly as deleted, which might trigger other instantiations or implicit definitions. If errors occur during that “generating instantiations and implicit definitions” stage then they’re errors, but if that succeeds but during substitution an expression in the function template signature turns out to be invalid e.g. because it uses a member that doesn’t exist or something that got implicitly defined as deleted, that’s not an error, just a deduction failure.

So the mental model I use is that substitution needs to do a “preparation” step first to generate types and members, which might cause hard errors, but once we have all the necessary generation done, any further invalid uses are not errors. Of course all this does is move the problem from “what does immediate context mean?” to “Which types and members need to be generated before this substitution can be checked?” so it may or may not help you!

Leave a Comment