By Andy Prowl


2013-03-07 00:06:31 8 Comments

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?

2 comments

@Jonathan Wakely 2013-03-07 01:04:06

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!

@Andy Prowl 2013-03-07 01:07:25

Thank you for the very exhaustive and detailed explanation

@Jonathan Wakely 2013-03-07 01:09:57

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!

@Andy Prowl 2013-03-07 01:15:52

I do find it helpful, it doesn't matter if it is non-normative as long as it enriches my perspective

@Nawaz 2013-03-17 17:58:28

@AndyProwl: See this somewhat related topic : SFINAE, deduction vs. instantiation

@Andy Prowl 2013-03-17 18:05:56

@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 explanation

@Nawaz 2013-03-17 20:13:42

+1 Liked this explanation. (+1 to the question also).

@ustulation 2014-11-02 15:03:56

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?

@Jonathan Wakely 2014-11-03 03:02:32

@ustulation, matching class template partial specializations uses the same rules as template argument deduction for function templates.

@ustulation 2014-11-03 08:34:16

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 specialization

@Jonathan Wakely 2014-11-10 14:40:07

@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>.

@Xeo 2013-03-07 00:18:41

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.

@Andy Prowl 2013-03-07 00:43:58

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>?

@Xeo 2013-03-07 00:45:30

@Andy: Because it isn't. ;) ... will also accept zero arguments, unlike the other overload.

@Andy Prowl 2013-03-07 00:46:56

For the series: "I should go to bed"...

@sellibitze 2013-03-07 13:14:48

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/c075b74ae0b052f2

@Xeo 2013-03-07 13:16:11

@sellibitze: Yeah, correct, since using aliases are basically template-level macros (atleast that's how I think of them).

@ustulation 2014-11-02 09:26:00

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?

@Xeo 2014-11-02 09:33:19

@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.

@ustulation 2014-11-02 10:30:18

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.

Related Questions

Sponsored Content

1 Answered Questions

[SOLVED] Private member access in template substitution and SFINAE

2 Answered Questions

[SOLVED] Does SFINAE depend on type deduction?

2 Answered Questions

[SOLVED] Implicit special functions: when would they be ill-formed?

  • 2018-03-16 11:37:38
  • YSC
  • 791 View
  • 16 Score
  • 2 Answer
  • Tags:   c++ c++11 c++03

2 Answered Questions

[SOLVED] How does `void_t` work

1 Answered Questions

[SOLVED] How does substitution work in template argument deduction?

  • 2016-08-16 03:29:37
  • Carousel
  • 172 View
  • 6 Score
  • 1 Answer
  • Tags:   c++ substitution

3 Answered Questions

[SOLVED] declval expression (for SFINAE) with std::ostream

  • 2016-05-27 10:13:35
  • Siler
  • 1553 View
  • 17 Score
  • 3 Answer
  • Tags:   c++ sfinae

1 Answered Questions

[SOLVED] Why does the order of template argument substitution matter?

1 Answered Questions

Sponsored Content