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

Почему этот зависимый поиск имени находит глобальный идентификатор вместо метода?

Когда компилятор пытается разрешить i.template hi<T>();, он находит hi в глобальном пространстве имен вместо метода hi на i (ideone). Почему?

#include <cstdio>

// Define 'hi' and 'bye' in the global namespace; these should *not* be used
template<typename T> struct hi { };
template<typename T> struct bye { };

// Foo needs to be templated for Foo::Inner to be a dependent type (I think)
template<typename T>
struct Foo
{
    struct Inner {
        // This is a plain-old templated member function of Inner, yes?
        template<typename U>
        void hi() { std::printf("hi!\n"); }

        // This is a plain-old member function of Inner
        void bye() { std::printf("bye!\n"); }
    };

    void sayStuff()
    {
        Inner i;
        i.template hi<T>();   // Fails to compile -- finds global hi instead of member
        i.bye();              // Compiles fine, finds member
    }
};

int main() {
    Foo<int> f;
    f.sayStuff();
    return 0;
}

Я использую g++ 4.9.1/4.9.2 (-std=c++11). Точное сообщение об ошибке:

prog.cpp: In member function 'void Foo<T>::sayStuff()':
prog.cpp:19:5: error: invalid use of 'struct hi<T>'
   i.template hi<T>();
     ^

Этот код отлично работает с Clang и VS2013, но генерирует ошибку в g++ и EDG. Но какие компиляторы правы?

Есть ли способ разрешить это, кроме изменения имени члена? В моем реальном коде конфликт возникает, когда тип из пространства имен std (который был импортирован через using namespace std, скажем) имеет то же имя, что и одна из моих функций-членов. Очевидно, мне хотелось бы, чтобы мой код реализации был надежным и не вызывал случайных конфликтов имен в коде пользователя.

4b9b3361

Ответ 1

Насколько мне известно, что происходит.

DR228 говорит:

[Проголосовал за WP на собрании в апреле 2003 года.]

Рассмотрим следующий пример:

template<class T>
struct X {
   virtual void f();
};

template<class T>
struct Y {
  void g(X<T> *p) {
    p->template X<T>::f();
  }
};

Это ошибка, потому что X не является шаблоном-членом; 14.2 [temp.names] в пункте 5 говорится:

Если имя, предваряемое шаблоном ключевого слова, не является именем шаблона-члена, программа плохо сформирована.

В некотором смысле это имеет смысл: X считается шаблоном с использованием обычного поиска, хотя p имеет зависимый тип. Тем не менее, я думаю, что это делает использование префикса шаблона еще более трудным для обучения.

Было ли это намеренно объявлено вне закона?

Предлагаемое решение (4/02):

Исключить первое использование слова "член" в пункте 14.2 [temp.names], чтобы его первое предложение гласило:

Если имя, предваряемое шаблоном ключевого слова, не является именем шаблона, программа плохо сформирована.

Однако в самом современном общедоступном проекте стандарта С++ N4296 в §14.2.5 появляется следующая формулировка:

Имя, предваряемое шаблоном ключевого слова, должно быть идентификатором шаблона, или имя должно ссылаться на шаблон класса. [Примечание. Ключевое слово template может не применяться к элементам шаблонов классов без шаблонов. -end note] [Примечание: как и в случае с префиксом typename, префикс template допускается в случаях, когда это не является строго необходимым; то есть, когда спецификатор вложенного имени или выражение слева от -> или . не зависит от параметра шаблона, или использование не отображается в области шаблона. -end note]

[Пример:

template <class T> struct A {
  void f(int);
  template <class U> void f(U);
};

template <class T> void f(T t) {
  A<T> a;
  a.template f<>(t); // OK: calls template
  a.template f(t); // error: not a template-id
}

template <class T> struct B {
  template <class T2> struct C { };
};
// OK: T::template C names a class template:

template <class T, template <class X> class TT = T::template C> struct D { };
D<B<int> > db;

-end пример]

Эта формулировка звучала аналогично, но достаточно различна, чтобы копать. Я обнаружил, что в проекте N3126 текст был изменен на эту версию.

Мне удалось связать это изменение с этим DR96:

Ниже приведена формулировка из 14.2 [temp.names] абзацев 4 и 5, в которой обсуждается использование ключевого слова "шаблон" . или → и в квалифицированных именах.

{надрез}

Весь смысл этой функции состоит в том, чтобы сказать, что ключевое слово "шаблон" необходимо для указания того, что "<" начинает список параметров шаблона в определенных контекстах. Ограничения в параграфе 5 оставляют открытыми для обсуждения определенных случаев.

Во-первых, я думаю, должно быть более ясно, что вместо имени шаблона должен следовать список аргументов шаблона, когда в этих контекстах используется ключевое слово "шаблон" . Если мы этого не сделаем, нам придется добавить несколько семантических разъяснений. Например, если вы скажете "p- > template f()", а "f" - это набор перегрузки, содержащий как шаблоны, так и nontemplates: a) это действительно? б) не игнорируются ли теги в наборе перегрузки? Если пользователь вынужден писать "p- > template f < > (), то ясно, что это действительно так, и одинаково ясно, что nontemplates в наборе перегрузки игнорируются. Поскольку эта функция была добавлена ​​исключительно для обеспечения синтаксического руководства, я думаю, что важно, чтобы в противном случае они не имели семантических последствий.

По сути, очень тонкое изменение DR228 было утрачено во время последующей ревизии; однако, поскольку никакого подобного ограничения не было установлено, намерение DR228, вероятно, все еще сохраняется, если в стандарте не было другого пересмотра. Это означает, что поиск шаблона должен происходить глобально в этом случае, даже если он является зависимым типом.

Посмотрите на наши правила поиска имен. §3.4.5.1:

В выражении доступа члена класса (5.2.5), если токен . или -> сразу же следует идентификатором, за которым следует <, идентификатор должен быть просмотрен, чтобы определить, будет ли < - это начало списка аргументов шаблона (14.2) или оператора меньше. Идентификатор сначала просматривается в классе выражения объекта. Если идентификатор не найден, он затем просматривается в контексте всего постфиксного выражения и называет шаблон класса.

Это явно указывает, что в baz.foo->template bar<T>(); Мы сначала рассмотрим контекст класса, включая стандартный поиск шаблона. После этого и если ничего не найдено, если форма выражения верна, мы переходим к контексту всего выражения. По существу, это продвигалось, и поиск этого имени должен выполняться таким же образом, если строка просто читается template bar<T>(); Действительно, хотя мы уже знали об этом с DR228. Я просто хотел дважды проверить и подтвердить. Реальный вопрос заключается в том, какой шаблон должен получить приоритет, тот, который находится в глобальной области видимости или в области видимости класса.

Для этого нам теперь нужно спросить о неквалифицированном поиске имени, потому что теперь бар рассматривается в том же контексте, что и foo, поэтому он больше не следит за правилами поиска членов, он следит за нормальными, неквалифицированными правилами поиска шаблонов, которые, естественно, предпочитают локальная версия.

Таким образом, в суммировании кажется, что Clang и MSVC демонстрируют правильное поведение, а GCC и EDG в этом случае не используются.

Мое лучшее предположение о том, почему GCC ошибается, выбирает неправильный контекст для назначения выражению после запуска правила. Вместо того чтобы помещать контекст на том же уровне, что и постфиксное выражение, оно может просто помещать его на глобальном уровне в случае аварии? Может быть, он просто пропускает первый шаг поиска? (Но это всего лишь предположение, я должен был бы выяснить, где искать в источнике GCC, чтобы сказать.) Это также объясняет, почему решение @Mikael Persson по изменению поиска к квалифицированному привело к тому, что компиляция началась снова.

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