Ambiguous member access expression: is Clang rejecting valid code?
-
27-02-2021 - |
Question
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();
}
Solution
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.
OTHER TIPS
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.
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
toTemplateClass<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.
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
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.
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>
.