Paragraph 14.8.2/8 of the C++11 Standard specifies the conditions under which a substitution failure shall or shall not result in a "hard" compilation error (thereby causing compilation to fail) or in a "soft" error which would just cause the compiler to discard a template from a set of candidates for overload resolution (without making compilation fail and enabling the well-known SFINAE idiom):
If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed if written using the substituted arguments. [ Note: Access checking is done as part of the substitution process. —end note ] Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure. [...]
The words "immediate context" appear only 8 times in the whole C++11 Standard, and each time they are followed by (or occur as part of) an instance of the following (non-normative) text:
[Note: The evaluation of the substituted types and expressions can result in side effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such side effects are not in the “immediate context” and can result in the program being ill-formed.—end note ]
The note gives a (not very generous) hint on what is meant by immediate context, but at least for me this is often not enough to decide whether a substitution is or is not supposed to cause a "hard" compilation error.
QUESTION:
Could you provide an explanation, a decision procedure, and/or some concrete examples to help figuring out in what cases a substitution error does and does not occur in the "immediate context" of the function type and its template parameter types?
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!
The immediate context is basically what you see in the template declaration itself. Everything outside of that is a hard error. Hard-error examples:
#include <type_traits>
template<class T>
struct trait{ using type = typename T::type; };
template<class T, class U = typename trait<T>::type>
void f(int);
void f(...);
template<class T, class U = typename T::type>
void g(int);
void g(...);
template<class>
struct dependent_false : std::false_type{};
template<class T>
struct X{
static_assert(dependent_false<T>(), "...");
using type = void;
};
int main(){
f<int>(0);
g<X<int>>(0);
}
Live version.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With