Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SFINAE, deduction vs. instantiation

Tags:

c++

standards

This code fails to compile in most compilers but at first I intuitively expected SFINAE to protect me:

typedef void (*A)();

template < typename T >
struct a_metafun { typedef typename T::type type; };

template < typename T >
typename a_metafun<T>::type f(T) {}

template < typename T>
void f(T(*)()) {}

int main() { f(A()); }

I can fix the problem in at least two ways:

  1. Change the definition of "metafun" f() to:

    template < typename T > typename T::type f(T) {}

  2. define a_metafun such that it analyzes T and has a type if T has one and doesn't if it doesn't...but instantiates without error either way:

    BOOST_MPL_HAS_XXX_TRAIT_DEF(type)
    
    typedef < template T, bool = has_type<T>::value >
    struct a_metafun { };
    
    typedef < template T >
    struct a_metafun<T, true> { typedef typename T::type type };
    

Upon looking at 14.8.2 (C++03) it looks to me like it specifies exactly under what conditions SFINAE can apply. Is there a better place to look? Failure within the instantiation of an already deduced template, even during the deduction of another, would not seem to be included in this list.

Another direction I've taken to interpreting what makes this illegal is that the deduction of a_metafun has already taken place and the instantiation of its innards is what is causing the error. SFINAE doesn't apply during instantiation but only during deduction, or am I wrong there? In the second case though, a_metafun is being correctly, and well-formedly instantiated but it simply has no "type" definition within, which means the template attempting to instantiate it is failing due to substitution.

Basically I'm wondering what in the standard specifies the behavior I'm witnessing. Every compiler I've tried complains, even comeau. I'm of the opinion that they are correct in doing so, I'm just not completely sure as to why.

So, experts...what's what? Why does the instantiation of type, even in the context of deduction in f() cause an error rather than SFINAE exclusion?

like image 624
Edward Strange Avatar asked Dec 31 '12 06:12

Edward Strange


2 Answers

In C++03 specification, the rule of SFINAE is a little vague, allowing compiler authors to go to any length to find substitution failure to result in SFINAE. The relevant text §14.8.2/2 from C++03 says,

- [...] If a substitution in a template parameter or in the function type of the function template results in an invalid type, type deduction fails [...]

It further explains few reasons for failure, but none of them actually says at what point the substitution failure should be considered as SFINAE. So I guess, your code may work fine in C++03 (or may not, depending on how the compiler authors interpret the text. It is confusing to me anyway).

But the wordings in C++11 has been improved removing the vagueness. It says in §14.8.2/8,

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 term "immediate context" is interesting, and I think it applies to your situation. More specifically, the substitution failure in the meta-function a_metafun is not considered "immediate context" of the function-type. It is ill-formed in C++11, not SFINAE.

But then, even though C++11 has introduced the phrase "immediate context" to make the text slightly better, still the definition of the phrase isn't clear enough. Here is one active issue:

  • 1844. Defining “immediate context”
like image 186
Nawaz Avatar answered Oct 14 '22 22:10

Nawaz


SFINAE won't protect you there, the error happens after type deduction. However, this should work:

template < typename T, typename Type = typename T::type >
struct a_metafun { typedef Type type; };

By accesing T::type in a default template parameter we cause this to happen at substitution time, and at that time SFINAE kicks in.

Edit: After thinking some more, I'm not exactly sure why your current implementation fails. I think is because a_metafun has a member type type, one that causes a compilation error; it would be different if a_metafun didn't have a member type type at all.

like image 2
K-ballo Avatar answered Oct 14 '22 20:10

K-ballo