Неоднозначное выражение доступа к членам: Clang отклоняет допустимый код?

У меня есть код, который для целей этого вопроса сводится к

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(); }

to

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

но я хотел бы быть уверенным в своем понимании того, когда можно исключить параметры шаблона.


РЕДАКТИРОВАТЬ: я думал, что двусмысленность была между двумя экземплярами TemplateClass. Следующий код компилируется с помощью GCC и Clang, что ставит под сомнение эту гипотезу:

class E {};

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

int main() {
  A<A<E> > a;
  a.A::method();
}

person Per    schedule 11.11.2011    source источник


Ответы (6)


Я считаю, что clang правильно отклоняет этот код.

Неоднозначность, которую обнаруживает 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]" стандартов C++11 и C++98.

Соответствующая часть 3.4.5/4:

Если выражение-идентификатора в доступе к члену класса является квалифицированным-идентификатором формы

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

[...]

Дело обстоит именно так. id-expression — это часть справа от ., и в нашем случае это полное имя TemplateClass::method.

[...]
имя-класса-или-имя-пространства-имени после оператора . или -> просматривается как в контексте всего postfix-expression и в рамках класса объектного выражения.

"Область действия всего постфиксного выражения" — это тело статической функции, и в этой статической функции TemplateClass относится к TemplateClass<B>, поскольку функция является членом этого класса (мы назвали TemplateClass<B>::static_method).

Таким образом, в этом контексте имя относится к TemplateClass<B>.

«Выражение объекта» — это часть слева от ., в нашем случае c. Класс c — это TemplateClass<A>, и в рамках этого класса TemplateClass относится к TemplateClass<A>.

Таким образом, в зависимости от области, используемой для поиска, имя относится к другому объекту.

Стандарт теперь говорит:

Если имя встречается в обоих контекстах, имя-класса-или-имя-пространства-имя должно ссылаться на один и тот же объект.

В нашей программе такого нет. Программа неправильно сформирована, и компилятор должен выдать диагностическое сообщение.

Неоднозначность останется прежней, если вы замените B на TemplateClass<A>, как указано в вопросе.

person sth    schedule 10.12.2011
comment
+1, см. мой ответ для бесплатного варианта шаблона - person curiousguy; 10.12.2011

В ISO/IEC 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.

person chill    schedule 14.11.2011
comment
С++ 98, в основном то же самое (14.6.1): в рамках шаблона класса, когда имя шаблона не уточняется и не сопровождается ‹, оно эквивалентно имени шаблона, за которым следуют параметры шаблона. заключен в ‹›. [Пример: конструктор для Set может называться Set() или Set‹T›(). ] На другие специализации (14.7.3) класса можно ссылаться, явно определяя имя шаблона с соответствующими аргументами шаблона. ... поэтому я согласен, что это выглядит как ошибка в Clang. - person 0xC0000022L; 17.11.2011
comment
@STATUS_ACCESS_DENIED, в C++98 это 14.6.1 [#1]. В рамках шаблона класса, когда имя шаблона не уточняется и не сопровождается <, оно эквивалентно имени шаблона, за которым следует параметрами шаблона, заключенными в <>. - person chill; 17.11.2011
comment
Учитывая, что в вызове метода a.A::method() во втором примере кода отсутствуют параметры шаблона, может ли A подпадать под действие других правил разрешения имен, к которым № 5 не применяется? - person Per; 18.11.2011
comment
@Per, насколько я понимаю, весь раздел 14.6 Разрешение имен [temp.res] применим только к разрешению имен в определении шаблона. Во втором случае, возможно, применим 3.4.5 Доступ к члену класса [basic.lookup.classref] [#4]. - person chill; 18.11.2011

Когда мы вызываем эти две строки:

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.

Я не знаю, является ли это поведение частью стандарта С++.

ИЗМЕНИТЬ:

На самом деле случай № 3 не является кастингом, это квалифицированные имена. Итак, в заключение, Clang не знает об этом синтаксисе квалификации, в то время как GCC и Visual C++ 2010 знают.

person kol    schedule 14.11.2011
comment
Из какого компилятора были получены выходные данные? - person Per; 14.11.2011
comment
На самом деле кейс №3 не кастинг, это квалифицированные имена. - person kol; 14.11.2011
comment
@Per Вывод GCC 3.4.2: 1: 13TemplateClassIS_I10EmptyClassEE 2: 13TemplateClassIS_I10EmptyClassEE 3: 13TemplateClassI10EmptyClassE 4: 13TemplateClassIS_I10EmptyClassEE Это гораздо менее ясно (по крайней мере, для меня). - person kol; 14.11.2011
comment
Что делает VC++ с исходным листингом? - person Per; 14.11.2011

Не ответ,

просто мой небольшой вклад:

Удаление шаблонов, но сохранение тех же имен:

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), в некоторых случаях может привести к неоднозначности.

person curiousguy    schedule 10.12.2011

Никогда не пользовавшийся Clang, меня очень интересовала эта проблема. (Ирония судьбы, да, я знаю.)

Совместимость Clang C++ указывает, что есть несколько вещей, касающихся шаблонов, которые обрабатывают другие компиляторы (особенно GCC). на что будет жаловаться. Это вещи, которые слабо определены в стандарте ("ну нельзя же такое допускать... но можно"); почти все они включают шаблоны. Ни один из них в точности не похож на вашу проблему, но они достаточно близки, чтобы быть информативными, и, безусловно, их стоит прочитать.

Таким образом, это не похоже на то, что Clang сломан — просто Clang более разборчив, чем другие.

person John Price    schedule 16.11.2011
comment
Кланг и Комо отвергают; GCC и VC++ принимают. Обычно я бы проголосовал за первую группу, но никто не нашел в стандарте формулировки, которая ставит под сомнение цитирование @chill в пользу отсутствия двусмысленности. - person Per; 17.11.2011
comment
@Чем больше я на это смотрю, тем больше это похоже на Неквалифицированный поиск в зависимых базах шаблонов классов (qv). Имеет ли значение уточнение поиска с помощью this-> (this->u.TemplateClass::method();)? К сожалению, я не могу понять, как читать их ссылки (не могу найти их список ссылок), или я мог бы проверить дальше. - person John Price; 17.11.2011
comment
Это статический метод, поэтому я не понимаю, как выполнить предложенный вами тест. - person Per; 17.11.2011
comment
@Самый простой способ проверить это - скопировать подпрограмму в нестатический метод в шаблоне и посмотреть, появится ли та же ошибка. Если да, поставьте перед ним this-> и посмотрите, поможет ли это. В качестве альтернативы вы можете использовать трюк static this (сохранить this в статической переменной, содержащейся в шаблоне, и использовать эту переменную как поддельную this). - person John Price; 17.11.2011
comment
Та же ошибка, если я удалю static из определения static_method. Непонятно, как втиснуть this в нестатический вызов метода объекта u. - person Per; 18.11.2011
comment
@JohnPrice Имеет ли значение уточнение поиска с помощью this-> (this->u.TemplateClass::method();)? учитывая, что тип u в любом случае является параметром шаблона - person curiousguy; 10.12.2011

Я думаю, двусмысленность в том, что TemplateClass дважды находится в наследстве TemplateClass : (TemplateClass : EmptyClass)

u.TemplateClass::method(); означает u.TemplateClass<TemplateClass<EmptyClass> >::method(); или u.TemplateClass<EmptyClass> >::method();?

Возможно, GCC имеет стандартное право, но в любом случае вы должны добавить <T>.

person Pubby    schedule 11.11.2011
comment
Нет. Это вызывает TemplateClass<TemplateClass<EmptyClass> >::method. Я хочу TemplateClass<EmptyClass>::method. - person Per; 12.11.2011
comment
@Per Упс. Я обновил свой ответ, хотя это всего лишь предположение. - person Pubby; 12.11.2011