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

Поиск зависимых имен в экземпляре экземпляра С++

Когда я пытаюсь скомпилировать этот код

// void foobar(int); 

template <class T>
struct Foo {
  void bar(T t) { foobar(t); };
};

void foobar(int);

template class Foo<int>;

с g++ 4.8.2 Появляется следующее сообщение об ошибке

foo.cc: In instantiation of ‘void Foo<T>::bar(T) [with T = int]’:
foo.cc:10:16:   required from here
foo.cc:5:27: error: ‘foobar’ was not declared in this scope, and no 
             declarations were found by argument-dependent lookup at 
               the point of instantiation [-fpermissive]
   void bar(T t) { foobar(t); };
                           ^
foo.cc:8:6: note: ‘void foobar(int)’ declared here, later in the translation unit
 void foobar(int);
      ^

(С clang 3.4 это почти то же самое).

Во-первых, я думаю, что код правильный и должен компилироваться, поскольку foobar является зависимым именем в объявлении шаблона и должен быть просмотрен только в фазе два при создании экземпляра шаблона. Когда это делается в последней строке, "foobar (int)" уже объявлен. Код компилируется, BTW, когда я раскомментирую самую верхнюю строку, но обе декларации до создания экземпляра, и поэтому это не имеет значения.

Во-вторых, само сообщение об ошибке кажется мне противоречивым. В нем говорится: "Никаких деклараций не было найдено в момент становления", то есть foo.cc:10:16, и он говорит, что он объявлен "позже" в foo.cc:8:6. Для всего, что я знаю о числах и английском языке, я бы назвал это "раньше", а не "позже".

Итак, это ошибка в gcc или у меня что-то не так? Так как это кажется мне общей моделью использования, я не могу поверить в это, однако.

Кстати: когда я тестирую второй пример "Разрешение имен для зависимых типов" в MSDN (http://msdn.microsoft.com/en-us/library/dx2zs2ee.aspx) с g++, результат отличается от vС++, который (не в общем, но в этом конкретном случае) подорвал бы это, будучи ошибкой в ​​g++.

4b9b3361

Ответ 1

tl; dr Foo<int> не вызывает какой-либо ADL, но Foo<X> будет (где X - тип класса).


Прежде всего, в этом коде foobar есть зависимое имя из-за (С++ 14/N3936) [temp.dep]/1

В выражении вида:

postfix-expression ( expression-list opt )

где постфиксное выражение является неквалифицированным-id, unqualified-id обозначает зависимое имя, если [...]

  • любое из выражений в списке выражений является зависимым от типа выражением (14.6.2.2) или

и t - зависимое имя, потому что оно является частью объявления T t, где t является параметром шаблона и, следовательно, зависимым типом.

Переходя на зависимое разрешение имен, существует [temp.dep.res]/1, в котором говорится о том, что имена могут отображаться как в контексте определения, так и в контексте контекста, и определяет, где контекст создания. Я пропустил это для краткости, но в этом примере template class Foo<int>; является точкой инстанцирования.

Следующий бит [temp.dep.candidate]/1:

Для вызова функции, где постфиксное выражение является зависимым именем, функции-кандидаты обнаруживаются с использованием обычных правил поиска (3.4.1, 3.4.2), за исключением того, что:

  • Для части поиска, использующей поиск неквалифицированных имен (3.4.1), найдены только объявления функций из контекста определения шаблона.
  • Для части поиска с использованием связанных пространств имен (3.4.2) найдены только объявления функций, найденные в контексте определения шаблона или контекста создания шаблона.

Последние две части - это "две фазы" двухфазного поиска. (Примечание - этот раздел изменен в формулировке от С++ 11 до С++ 14, но эффект в том же).

В первой фазе, 3.4.1, имена foobar не найдены.


Итак, мы переходим на вторую фазу. Фактические места, названия которых выглядят, как описано в 3.4.2. Текст длинный, но вот два из соответствующих правил:

  • Если T является фундаментальным типом, его ассоциированные множества пространств имен и классов являются пустыми.

  • Если T - тип класса (включая объединения), его ассоциированные классы: сам класс; класс которого он является членом, если таковой имеется; и его прямые и косвенные базовые классы. Его связанные пространства имен являются внутренними охватывающими пространствами имен связанных классов. [...]

Итак, когда вы создаете экземпляр Foo<int>, тогда вторая фаза поиска не вводит никаких дополнительных пространств имен для поиска.

Однако, если вы измените свой пример на struct X {};, а затем измените int на X всюду, тогда код скомпилируется. Это связано с последней точкой маркера: ADL для аргумента типа класса выполняет поиск охватывающего пространства имен этого класса (который теперь является глобальным пространством имен), однако ADL для аргумента встроенного типа не ищет глобальное пространство имен.

Ответ 2

Поиск в фазе 2 включает только правила поиска имен, которые не могут применяться, например, в фазе 1-ADL. Двухфазный поиск - это именно то, что некоторые имена просматриваются в первой фазе, а некоторые из них рассматриваются во второй фазе. Этот конкретный вид называется именем фазы, так как компилятор отлично способен искать foobar в пространстве имен функции во время первой фазы.

Visual С++ не реализует поиск двухфазных имен.

Ответ 3

Похоже на меня. Хотя разрешение перегрузки выполняется только в фазе 2, в фазе 1 вы уже должны знать, что foobar(t) является выражением вызова функции. Если foobar называет тип, t даже не будет зависимым именем.