Неоднозначное выражение доступа к члену: отклоняет ли Clang допустимый код? - PullRequest
23 голосов
/ 12 ноября 2011

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

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

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


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

class E {};

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

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

Ответы [ 6 ]

11 голосов
/ 10 декабря 2011

Я считаю, что 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:

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

7 голосов
/ 14 ноября 2011

В ИСО / МЭК 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.

4 голосов
/ 14 ноября 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.

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

EDIT:

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

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

1 голос
/ 16 ноября 2011

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

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

Так что, похоже, что Clang не работает -- просто Кланг придирчивее других.

0 голосов
/ 12 ноября 2011

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

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

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

...