What exactly is the "immediate context" mentioned in the C++11 Standard for which SFINAE applies?
Asked Answered
S

3

50

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?

Stoppage answered 7/3, 2013 at 0:6 Comment(3)
Also, see Solving the SFINAE problem for expressions.Frasch
@JesseGood: Yes, I've read that, but it helps only in part and the bullet list isn't very clearStoppage
Related DR : `Defining “immediate context”Swainson
D
50

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!

Dismount answered 7/3, 2013 at 1:4 Comment(10)
Thank you for the very exhaustive and detailed explanationStoppage
it's by no means authoritative though, it's just my mental model, which has been in constant evolution, and needed regular revision when it fails me!Dismount
I do find it helpful, it doesn't matter if it is non-normative as long as it enriches my perspectiveStoppage
@AndyProwl: See this somewhat related topic : SFINAE, deduction vs. instantiationSwainson
@Nawaz: Thank you :) I had found that Q&A, but I was not sure what the formal meaning of "immediate context" was. I still believe this should be defined more formally in the Standard, but Jonathan's answer gives a nice explanationStoppage
great explanation. i'm trying to wrap my head around SFINAE and this helps, but now I'm stuck again. SFINAE can be used without function templates too (eg., to pick/discard class template specializations). In that case the template will be instantiated right? Because there are no separate deduction and substitution steps like for function templates that you explain here. How does that work in light of what you wrote here?Amir
@ustulation, matching class template partial specializations uses the same rules as template argument deduction for function templates.Dismount
so the way i should interpret this be: the base template should be well formed. If that is fine then specializations are inspected and if anything that goes wrong in /*here*/ in the following template<..> struct A<../*here*/..> {...} then that specialization is discarded. Is that close enough? I tried this but it gives an error in place where i want to choose the base-case :( SFINAE for class template specializationAmir
@ustulation, please ask your own question instead of hijacking comments on another question. Your interpretation is wrong, the specialization is not just discarded "if anything goes wrong" but only if what goes wrong is in the immediate context. T::value is not in the immediate context, because of the pointless indirection through Error. It works if you just use void_t<T::value>.Dismount
@jackX that has nothing to do with the "immediate context", so you should ask your own question instead of hijacking these comments.Dismount
H
9

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.

Hypochondriac answered 7/3, 2013 at 0:18 Comment(7)
I will try to clarify my question a bit better with an example. Why is this a soft error? Why is it considered to be in the "immediate context", even though the error is caused in a nested context when instantiating T2<int>?Stoppage
@Andy: Because it isn't. ;) ... will also accept zero arguments, unlike the other overload.Hypochondriac
Template aliases are also considered "immediate context". It's not clear from the standard but that is the intention according to a committee member. See groups.google.com/group/comp.std.c++/msg/c075b74ae0b052f2Buran
@sellibitze: Yeah, correct, since using aliases are basically template-level macros (atleast that's how I think of them).Hypochondriac
But changing f<int>(0); to f(0); succeeds calling the variadic function. In this case too it should implicitly do the same as the former and fail, no?Amir
@ustulation: No, if you leave out the <int> part, you can't call the first overload of f anymore - note that T is not deduced, it has to be explicitly provided. As such, the variadic overload is obviously chosen.Hypochondriac
ohk..i see..if you changed it to template<class T, class U = typename trait<T>::type> void f(T) {} then it does fail. That is what I's trying to refer to..because I had found an example in SO somewhere that specifying the type explicitly causes failure and not specifying explicitly gets SFNIAE to discard it. I can't find it right now though. I couldn't understand it then.Amir
P
0

According to Marius Bancila's Template Metaprogramming with C++_ Learn everything about C++ templates and unlock the power of template metaprogramming, page 199:

SFINAE only applies in the so-called immediate context of a function. The immediate context is basically the template declaration (including the template parameter list, the function return type, and the function parameter list).

Prunelle answered 19/1, 2023 at 12:58 Comment(1)
Simply quoting from another source is not really an acceptable answer on Stack Overflow. You should at least add some explanation to your answer as to how/why a citation is relevant. Otherwise, your post may be considered plagiarism (even though you have cited the source).Portraitist

© 2022 - 2024 — McMap. All rights reserved.