Подтвердить что ты не робот

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

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

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

Ответ 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>.