Ambiguous member access expression: is Clang rejecting valid code?
Asked Answered
P

6

23

I have some code that, for the purposes of this question, boils down to

template<typename T>
class TemplateClass : public T {
 public:
  void method() {}
  template<typename U>
  static void static_method(U u) { u.TemplateClass::method(); }
};

class EmptyClass {};

int main() {
  TemplateClass<TemplateClass<EmptyClass> > c;
  TemplateClass<EmptyClass>::static_method(c);
}

I've tried to compile it with several versions of two compilers. GCC 4.2, 4.4, 4.6 accept it without complaint. Clang 2.9 and SVN trunk as of November 14 reject it with the following error message:

example.cc:6:38: error: lookup of 'TemplateClass' in member access expression is
      ambiguous
  static void static_method(U u) { u.TemplateClass::method(); }
                                     ^
example.cc:13:3: note: in instantiation of function template specialization
      'TemplateClass<EmptyClass>::static_method<TemplateClass<TemplateClass<EmptyClass>
      > >' requested here
  TemplateClass<EmptyClass>::static_method(c);
  ^
example.cc:2:7: note: lookup in the object type
      'TemplateClass<TemplateClass<EmptyClass> >' refers here
class TemplateClass : public T {
      ^
example.cc:2:7: note: lookup from the current scope refers here
1 error generated.

Which one is wrong? I can work around Clang by changing

  static void static_method(U u) { u.TemplateClass::method(); }

to

  static void static_method(U u) { u.TemplateClass<T>::method(); }

but I'd like be confident in my understanding of when it's OK to elide the template parameters.


EDIT: I had thought that the ambiguity was between the two instantiations of TemplateClass. The following code compiles with GCC and Clang, calling that hypothesis into doubt:

class E {};

template<typename T>
class A : public T {
 public:
  void method() {}
};

int main() {
  A<A<E> > a;
  a.A::method();
}
Porfirioporgy answered 11/11, 2011 at 21:59 Comment(0)
B
11

I believe that clang is correctly rejecting this code.

The ambiguity that clang finds can be reproduced with a less complicated example:

template<typename T>
class TemplateClass {
 public:
  void method() {}
  template<typename U>
  static void static_method(U u) { u.TemplateClass::method(); }                                  
};

struct A {};
struct B {};

int main() {
  TemplateClass<A> c;
  TemplateClass<B>::static_method(c);
}

Here the inheritance in the template is omitted and two independent classes are used for the instantiations. The error produced by clang remains the same.

First of all, in the scope of TemplateClass<T> the name TemplateClass refers to TemplateClass<T>, due to class name injection. This is the reason that the static method can use TemplateClass::method instead of a more explicit TemplateClass<T>::method.

The name lookup used to interpret u.TemplateClass::method in the static method is defined in "3.4.5 Class member access [base.lookup.classref]" of the C++11 and C++98 standards.

The relevant part is 3.4.5/4:

If the id-expression in a class member access is a qualified-id of the form

class-name-or-namespace-name::...

[...]

This is the case here. The id-expression is the part to the right of the . and in our case this is the qualified name TemplateClass::method.

[...]
the class-name-or-namespace-name following the . or -> operator is looked up both in the context of the entire postfix-expression and in the scope of the class of the object expression.

The "scope of the entire postfix-expression" is the body of the static function, and in this static function TemplateClass refers to TemplateClass<B>, since the function is a member of that class (we called TemplateClass<B>::static_method).

So in this scope the name refers to TemplateClass<B>.

The "object expression" is the part left of ., in our case c. The class of c is TemplateClass<A> and in the scope of this class, TemplateClass refers to TemplateClass<A>.

So, depending on the scope used for the lookup, the name refers to a different entity.

The standard now says:

If the name is found in both contexts, the class-name-or-namespace-name shall refer to the same entity.

This is not the case in our program. The program is ill-formed, and the compiler is required to give a diagnostic message.

The ambiguity stays the same if you replace B with TemplateClass<A>, as used in the question.

Biotype answered 10/12, 2011 at 0:23 Comment(1)
+1, see my "answer" for template free variantStaging
G
7

In ISO/IEC 14882:2011(E), "14.6.1 Locally declared names [temp.local]", [#5] says:

When the normal name of the template (i.e., the name from the enclosing scope, not the injected-class-name) is used, it always refers to the class template itself and not a specialization of the template.[ Example:

template<class T> class X {
    X* p;    // meaning X<T>
    X<T>* p2;
    X<int>* p3;
    ::X* p4;    // error: missing template argument list
                // ::X does not refer to the injected-class-name
};
— end example ]

This leads me to believe that in your example u.TemplateClass::method(); is equivalent to u.TemplateClass<T>::method(); and if Clang gives an error in one case and compiles cleanly in the other case, then it's a Clang error.

Giantism answered 14/11, 2011 at 14:29 Comment(4)
C++ 98, is basically the same (14.6.1): "Within the scope of a class template, when the name of the template is neither qualified nor followed by <, it is equivalent to the name of the template followed by the template-parameters enclosed in <>. [Example: the constructor for Set can be referred to as Set() or Set<T>(). ] Other specializations (14.7.3) of the class can be referred to by explicitly qualifying the template name with the appropriate template-arguments." ... so I would agree that this looks like an error in Clang.Thoreau
@STATUS_ACCESS_DENIED, in the C++98, it's 14.6.1 [#1] "Within the scope of a class template, when the name of the template is neither qualified nor followed by <,it is equivalent to the name of the template followed by the template-parameters enclosed in <>."Giantism
Given that the method call a.A::method() in the second code sample is missing template parameters, could A be subject to different name resolution rules to which #5 does not apply?Porfirioporgy
@Per, my understanding is that the whole section "14.6 Name resolution [temp.res]" is applicable only to resolution of names within a template definition. In the second case, perhaps "3.4.5 Class member access [basic.lookup.classref]" [#4] is applicable.Giantism
A
4

When we call these two lines:

TemplateClass<TemplateClass<EmptyClass> > c;
TemplateClass<std::string>::static_method(c);

then the type argument U is the type of the object c:

TemplateClass<TemplateClass<EmptyClass> >

Let's leave static_method, and do an experiment:

#include <iostream>
#include <typeinfo.h>

using namespace std;

template<typename T>
class TemplateClass : public T {
public:
  void method(int i) {
    cout << i << ": ";
    cout << typeid(*this).name() << endl; 
  }
};

class EmptyClass { };

void main() {
  TemplateClass<TemplateClass<EmptyClass> > u;
  u.method(1);
  u.TemplateClass::method(2);
  u.TemplateClass<EmptyClass>::method(3);
  u.TemplateClass<TemplateClass<EmptyClass> >::method(4);
}

The output is:

1: class TemplateClass<class TemplateClass<class EmptyClass> >
2: class TemplateClass<class TemplateClass<class EmptyClass> >
3: class TemplateClass<class EmptyClass>
4: class TemplateClass<class TemplateClass<class EmptyClass> >

In all four cases (and inside static_method) we call TemplateClass<T>::method, and the type name given between u. and :: will give the actual type T:

  • Case #1 is the default, here T is given by the declaration of u.
  • Case #4 is also trivial.
  • Case #2 looks as if the compiler should have guessed the type argument of TemplateClass, which is trivially the one given in the declaration of u.
  • Case #3 is very interesting. I guess function type casting happened here, from TemplateClass<TemplateClass<EmptyClass> >::method to TemplateClass<EmptyClass>::method.

I don't know whether this behavior is part of the C++ standard.

EDIT:

Actually case #3 is not casting, these are qualified names. So in conclusion, Clang is not aware of this qualification syntax, while both GCC and Visual C++ 2010 are.

Armbrecht answered 14/11, 2011 at 15:30 Comment(4)
Which compiler did the output come from?Porfirioporgy
Actually case #3 is not casting, these are qualified names.Armbrecht
@Porfirioporgy The output of GCC 3.4.2 is: 1: 13TemplateClassIS_I10EmptyClassEE 2: 13TemplateClassIS_I10EmptyClassEE 3: 13TemplateClassI10EmptyClassE 4: 13TemplateClassIS_I10EmptyClassEE This is much less clear (at least for me).Armbrecht
What does VC++ do with the original listing?Porfirioporgy
S
2

Not an answer,

just my small contribution:

Removing templates, but keeping the same names:

struct A {
    struct TemplateClass {
        void method() {}
    };
};
struct B {
    struct TemplateClass {
        void method() {}

        static void static_method(A::TemplateClass u) { 
            u.TemplateClass::method(); 
        }
    };
};

int main() {
    A::TemplateClass c;
    B::TemplateClass::static_method(c);
}

gives

Comeau C/C++ 4.3.10.1 (Oct  6 2008 11:28:09) for ONLINE_EVALUATION_BETA2
Copyright 1988-2008 Comeau Computing.  All rights reserved.
MODE:strict errors C++ C++0x_extensions

"ComeauTest.c", line 11: error: ambiguous class member reference -- type
          "B::TemplateClass::TemplateClass" (declared at line 7) used in
          preference to type "A::TemplateClass::TemplateClass" (declared at
          line 2)
            u.TemplateClass::method(); 
              ^

"ComeauTest.c", line 11: error: qualified name is not a member of class
          "A::TemplateClass" or its base classes
            u.TemplateClass::method(); 
              ^

2 errors detected in the compilation of "ComeauTest.c".

From N3242

Locally declared names [temp.local]

Like normal (non-template) classes, class templates have an injected-class-name (Clause 9). The injected class-name can be used with or without a template-argument-list. When it is used without a template-argument-list, it is equivalent to the injected-class-name followed by the template-parameters of the class template enclosed in <>.

(...)

Within the scope of a class template specialization or partial specialization, when the injected-class-name is not followed by a <, it is equivalent to the injected-class-name followed by the template-arguments of the class template specialization or partial specialization enclosed in <>.

(...)

A lookup that finds an injected-class-name (10.2) can result in an ambiguity in certain cases

Staging answered 10/12, 2011 at 4:9 Comment(0)
O
1

Having never used Clang, I was quite interested in this problem. (Ironic, yes I know.)

Clang C++ Compatibility indicates that there are several things regarding templates that other compilers (notably GCC) process that it will complain about. These are things that are weakly defined in the standard ("well, you shouldn't allow this ... but you can"); nearly all of them involve templates. None of these exactly look like your problem, but they're close enough to be informative -- and certainly worth a read.

So, it doesn't look like Clang is broken -- it's just that Clang is pickier than the others.

Officiary answered 16/11, 2011 at 19:37 Comment(6)
Clang and Comeau reject; GCC and VC++ accept. Normally I would vote with the former group, but no one has found language in the standard that calls into question @Giantism 's citation in favor of there being no ambiguity.Porfirioporgy
@Porfirioporgy the more I look at this, the more it looks like Unqualified lookup into dependent bases of class templates (q.v.). Does qualifying the lookup with this-> make a difference (this->u.TemplateClass::method();)? Unfortunately, I can't figure out how to read their references (can't find their reference list), or I could check further.Officiary
It's a static method, so I don't see how to perform the test you have suggested.Porfirioporgy
@Porfirioporgy the easiest way to test this would be to copy the routine to a non-static method in the template and see if you get the same error. If so, prepend with a this-> and see if that helps. Alternatively, you could use the static this trick (store this into a static variable contained in the template and use that variable as a fake this).Officiary
Same error if I delete static from the definition of static_method. It's not clear how to wedge this into a non-static method call on the object u.Porfirioporgy
@JohnPrice "Does qualifying the lookup with this-> make a difference (this->u.TemplateClass::method();)?" given that the type of u is a template parameter anywayStaging
P
0

I think the ambiguity is because TemplateClass is twice in the inheritance TemplateClass : (TemplateClass : EmptyClass)

Does u.TemplateClass::method(); mean u.TemplateClass<TemplateClass<EmptyClass> >::method(); or u.TemplateClass<EmptyClass> >::method(); ?

Perhaps GCC has the standard right, but whatever the case is you should add the <T>.

Piddling answered 11/11, 2011 at 22:7 Comment(2)
No. That calls TemplateClass<TemplateClass<EmptyClass> >::method. I want TemplateClass<EmptyClass>::method.Porfirioporgy
@Porfirioporgy Whoops. I updated my answer, although it's just a guess.Piddling

© 2022 - 2024 — McMap. All rights reserved.