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

GCC, Clang и IBM не согласны с тем, как выполнять зависящий от шаблона поиск имени. Какой из них прав?

Рассмотрим этот пример, который я нашел на веб-сайте IBM:

#include <iostream>
using namespace std;

void f(double) { cout << "Function f(double)" << endl; }

template<class T> void g(T a) {
  f(123);
  h(a);
}

void f(int) { cout << "Function f(int)" << endl; }
void h(double) { cout << "Function h(double)" << endl; }

void i() {
  extern void h(int);
  g<int>(234);
}

void h(int) { cout << "Function h(int)" << endl; }

int main(void) {
    i();
}

Что он напечатает?

  • В документации IBM я адаптировал этот пример из здесь, говорит, что он напечатает:

    Function f(double)
    Function h(double)
    

    Обоснованием этого является то, что поиск по параметру, зависящему от шаблона, выполняется непосредственно перед созданием i(), поэтому он находит h(double), но не h(int).

  • Когда я скомпилирую его с помощью GCC 4.4.1, он печатает:

    Function f(double)
    Function h(int)
    

    GCC, похоже, ищет зависимые от шаблона имена в шаблоне после компиляции всего остального, поэтому он находит как h(double), так и h(int), и предпочитает последний.

  • Когда я компилирую его с помощью Clang 2.8, он не компилируется. Ошибка компилятора:

    ibm_example.cc:8:3: error: use of undeclared identifier 'h'
      h(a);
      ^
    ibm_example.cc:16:3: note: in instantiation of function template specialization 'g<int>' requested here
      g<int>(234);
      ^
    1 error generated.
    

    Кажется, что Clang ищет зависимые от шаблона имена в шаблоне в точке, где объявлен шаблон, поэтому он не находит ни h(double), ни h(int).

Какой из них прав?

4b9b3361

Ответ 1

Они все правильные. На самом деле, не читайте...

template<class T> void g(T a) {
  f(123);
  h(a);
}

Здесь f является необязательным именем, но h является зависимым именем в соответствии с 14.6.2/1. f просматривается

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

f просматривается немедленно и привязывается к void f(double), только f, видимому в этой точке.

Согласно 14.6.4.1, момент создания void g<int>(int) сразу после определения void i(), где он используется.

[..] В противном случае точка инстанцирования для такой специализации сразу следует за объявлением или определением области пространства имен, которое относится к специализации.

Это означает, что источники для определения зависимого имени являются объявлениями, видимыми в определении template<class T> void g(T a) и "объявлениями из пространств имен, связанных с типами аргументов функции как из контекста создания (14.6.4.1), так и из контекста определения" (14.6.4).

Однако, поскольку int является базовым типом, набор связанных пространств имен пуст (3.4.2) (нет, даже не глобальное пространство имен включено), и в соответствии с 14.6.4.2 это только поиск с использованием связанных пространства имен, которые могут использовать контекст экземпляра шаблона, обычный поиск неквалифицированного имени может использовать только то, что видно в контексте определения шаблона. Это подтверждает то, что было сказано в 14.6.4.

Теперь, бонусная точка. 14.6.4.2 далее говорится:

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

Вызов плохо сформирован, потому что поиск не работает (часть связанных пространств имен здесь не применяется), поэтому поведение явно undefined, поэтому все может случиться. Следовательно, ни одно из проявлений поведения не свидетельствует о несоответствии стандарту, хотя, по моему, диагностика Clang больше всего соответствует стандарту.

(Все ссылки ISO/IEC 14882: 2011.)