Неоднозначное выражение доступа к члену: является ли Кланг отклоняющим действительный код?
У меня есть код, который для целей этого вопроса сводится к
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);
}
Я попытался скомпилировать его с несколькими версиями двух компиляторов. GCC 4.2, 4.4, 4.6 принимают его без жалобы. Clang 2.9 и SVN с 14 ноября отклоняют его со следующим сообщением об ошибке:
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.
Какая ошибка? Я могу работать вокруг Clang, изменяя
static void static_method(U u) { u.TemplateClass::method(); }
к
static void static_method(U u) { u.TemplateClass<T>::method(); }
но я хотел бы быть уверенным в своем понимании, когда он ОК, чтобы исключить параметры шаблона.
EDIT: Я думал, что двусмысленность была между двумя экземплярами TemplateClass
. Следующий код компилируется с GCC и Clang, что вызывает сомнения в этой гипотезе:
class E {};
template<typename T>
class A : public T {
public:
void method() {}
};
int main() {
A<A<E> > a;
a.A::method();
}
Ответы
Ответ 1
Я считаю, что clang правильно отклоняет этот код.
Неоднозначность, которая может быть воспроизведена с помощью менее сложного примера:
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);
}
Здесь наследование в шаблоне опущено, и для экземпляров используются два независимых класса. Ошибка, созданная clang, остается неизменной.
Прежде всего, в области TemplateClass<T>
имя TemplateClass
относится к TemplateClass<T>
, из-за инъекции имени класса. По этой причине статический метод может использовать TemplateClass::method
вместо более явного TemplateClass<T>::method
.
Поиск имени, используемый для интерпретации u.TemplateClass::method
в статическом методе, определен в разделе "3.4.5 Доступ к членам класса [base.lookup.classref]" из стандартов С++ 11 и С++ 98.
Соответствующая часть - 3.4.5/4:
Если id-выражение в доступе члена класса является квалифицированным идентификатором формы
class-name-or-namespace-name::...
[...]
Вот здесь. Идентификатор id является частью справа от .
, и в нашем случае это квалифицированное имя TemplateClass::method
.
[...]
имя класса-имени или пространства имен, следующих за оператором .
или ->
, просматривается как в контексте целое постфиксное выражение и в объем класса выражения объекта.
"Объем всего постфиксного выражения" является телом статической функции, и в этой статической функции TemplateClass
относится к TemplateClass<B>
, так как функция является членом этого класса (мы называем TemplateClass<B>::static_method
).
Итак, в этой области имя относится к TemplateClass<B>
.
"Объектное выражение" - это часть слева от .
, в нашем случае c
. Класс c
равен TemplateClass<A>
и в рамках этого класса TemplateClass
относится к TemplateClass<A>
.
Таким образом, в зависимости от области, используемой для поиска, имя относится к другому объекту.
В стандарте теперь говорится:
Если имя найдено в обоих контекстах, имя класса или имя пространства имен должно относиться к одному и тому же объекту.
Это не относится к нашей программе. Программа плохо сформирована, и компилятор должен предоставить диагностическое сообщение.
Неоднозначность остается прежней, если вы замените B
на TemplateClass<A>
, как используется в вопросе.
Ответ 2
В ИСО/МЭК 14882: 2011 (E), "14.6.1 Локально объявленные имена [temp.local]", [# 5] говорит:
Когда нормальное имя шаблона (т.е. имя из охватывающей области, а не имя введенного класса) используется, он всегда относится к самому шаблону класса, а не к специализации шаблона. [Пример:
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 ]
Это заставляет меня думать, что в вашем примере u.TemplateClass::method();
эквивалентен u.TemplateClass<T>::method();
, и если Clang дает ошибку в одном случае и компилируется в другом случае, то это ошибка Clang.
Ответ 3
Когда мы называем эти две строки:
TemplateClass<TemplateClass<EmptyClass> > c;
TemplateClass<std::string>::static_method(c);
тогда аргумент типа U является типом объекта c:
TemplateClass<TemplateClass<EmptyClass> >
Оставьте static_method
и выполните эксперимент:
#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);
}
Вывод:
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> >
Во всех четырех случаях (и внутри static_method
) мы называем TemplateClass<T>::method
, а имя типа, заданное между u.
и ::
, даст реальный тип T:
- Случай №1 по умолчанию, здесь T задается объявлением u.
- Дело № 4 также тривиально.
- Случай # 2 выглядит так, как будто компилятор должен был догадаться о аргументе типа TemplateClass, который тривиально тот, который указан в объявлении u.
- Случай №3 очень интересный. Я предполагаю, что здесь происходит кастинг типа функции, от
TemplateClass<TemplateClass<EmptyClass> >::method
до TemplateClass<EmptyClass>::method
.
Я не знаю, является ли это поведение частью стандарта С++.
EDIT:
На самом деле случай №3 не выполняется, это квалифицированные имена. Итак, в заключение, Clang не знает этого синтаксиса квалификаций, в то время как GCC и Visual С++ 2010.
Ответ 4
Не ответ,
просто мой небольшой вклад:
Удаление шаблонов, но сохранение одинаковых имен:
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);
}
дает
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".
От N3242
Локально объявленные имена [temp.local]
Подобно обычным (не шаблонным) классам, шаблоны классов имеют имя с введенным классом (раздел 9). Введенное имя класса может использоваться с или без шаблона-аргумента-списка. Когда он используется без списка шаблонов-аргументов, он эквивалентен имени введенного класса, за которым следуют шаблонные параметры класса шаблон, заключенный в < > .
(...)
В рамках специализации шаблона класса или частичной специализации, когда вместо имени с введенным классом не следует символ <, оно эквивалентно имени введенного класса, за которым следуют шаблонные аргументы шаблона класса специализации или частичной специализации, заключенной в < > .
(...)
Поиск, который обнаруживает имя введенного класса (10.2), может привести к двусмысленности в некоторых случаях
Ответ 5
Никогда не использовав Clang, я очень заинтересовался этой проблемой. (Иронично, да, я знаю.)
Clang С++ Compatibility указывает, что есть несколько вещей, связанных с шаблонами, которые другие компиляторы (особенно GCC) обрабатывают, на которые он будет жаловаться. Это вещи, которые слабо определены в стандарте ( "ну, вы не должны этого допускать... но можете" ); почти все из них связаны с шаблонами. Ничто из этого не похоже на вашу проблему, но они достаточно близки, чтобы быть информативными - и, безусловно, стоит прочитать.
Итак, это не похоже на то, что Клан разбит - это просто, что Кланг более узок, чем другие.
Ответ 6
Я думаю, что двусмысленность заключается в том, что TemplateClass
дважды в наследовании TemplateClass : (TemplateClass : EmptyClass)
Значит ли u.TemplateClass::method();
u.TemplateClass<TemplateClass<EmptyClass> >::method();
или u.TemplateClass<EmptyClass> >::method();
?
Возможно, GCC имеет стандартное право, но в любом случае вы должны добавить <T>
.