10

Clang-3.2 can compile and code behave as expected:

struct have_f { int f(int i) {return 10;} }; 

struct empty {};

template <class T> 
struct outer {
        T t; 

        // if T have f(), define  outer_f()
        template<class U=decltype(t.f(1))> 
        int outer_f(int i) { return t.f(i); }
};

int main() {
        outer<have_f>  o1;
        outer<empty>   o2;

        // to silence unused var warning
        return  o1.outer_f(10) + sizeof(o2); 
}

GCC of any version rejects with:

t.cc:13:6: error: ‘struct empty’ has no member named ‘f’
  int outer_f(int i) { return t.f(i); }
      ^

Who is correct? Gcc or Clang?

Note, that there was similar question, without real answer.

Community
  • 1
  • 1
Leonid Volnitsky
  • 8,854
  • 5
  • 38
  • 53
  • 1
    Well, it obviously _doesn't_ have `f`, does it? – SingerOfTheFall Oct 10 '12 at 06:37
  • 1
    @SingerOfTheFall I agree with you, but the question is not about why it not works, but which compiler is standard compliant. – nikolas Oct 10 '12 at 06:38
  • 1
    @SingerOfTheFall: The question is about SFINAE though. If SFINAE is supposed to kick in, then the method should be instantiated and the code and Clang are correct; if SFINAE is *not* supposed to kick in, then the code is wrong and gcc is correct. I believe Clang is correct here as template default values have been used for SFINAE for a long time. – Matthieu M. Oct 10 '12 at 06:44
  • @MatthieuM. The template method is not used for object `o2`, therefore it shouldn't be instantiated. If it was used by the `o2` object at the end, it should break the compilation, because `Empty` has no method f. That means in this case clang is right, and gcc wrong. Is my reasoning correct? – BЈовић Oct 10 '12 at 07:42
  • gcc bugzilla is full of SFINAE errors - is this related to your problem: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=51047? – PiotrNycz Oct 10 '12 at 08:48
  • 4
    I cannot tell you whether the code should be rejected or not and I don't know if you are interested in pragmatical workarounds but I know that making `T` 'dependent' again ensures that GCC performs SFINAE: `template().f(1))>`. – Luc Danton Oct 10 '12 at 08:59
  • 1
    this is not an sfinae error. sfinae does not apply because T when you instantiate the enclosing class template is already substituted. the only question is when the hard error isrisen, which depends on when the template default argument is instantiated (I dont know it offhead). – Johannes Schaub - litb Oct 10 '12 at 08:59

1 Answers1

9

I believe the issue is 14.6.3 [temp.nondep]:

1 - Non-dependent names used in a template definition are found using the usual name lookup and bound at the point they are used.

The example given describes that an ill-formed expression within the template definition "could be diagnosed either [in the template definition] or at the point of instantiation".

The default template-argument (14.1p9) U=decltype(t.f(1)) is a non-dependent name within the context of the instantiation of struct outer (that is, it does not depend on a template argument to its own template) so it is ill-formed for the instantiation of struct outer with T = struct empty. The standard doesn't explicitly describe where default template-arguments are evaluated, but the only sensible conclusion is that they are treated as any other construct and evaluated at the point they occur (or, in this example, at the point of instantiation of the struct outer template). I don't see any latitude for the compiler to delay evaluation of non-dependent default template-arguments to contexts where SFINAE applies.

Fortunately the solution is easy: just make the default template-argument U a dependent name:

    // if T have f(), define  outer_f()
    template<class T2 = T, class U=decltype(static_cast<T2 &>(t).f(1))> 
    int outer_f(int i) { return t.f(i); }
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • 2
    pity that they are not handled like *default arguments*. because default arguments are handled like template definitions for the purpose of instantiation. so if it would have been a default argument he would have been given the error only when trying to use the function template. but unfortunately what he has is a default *template* argument instead. – Johannes Schaub - litb Oct 10 '12 at 09:34