Почему этот компилятор с перегрузкой/пространством имен/шаблоном С++ не компилируется?

Вот код С++:

namespace A {

int f(int x) { return 0; }
int f(long x) { return 1; }

template<class T> int g(T x) {
  return f(x);
}

}

namespace B {
struct C {};
}

namespace A {
int f(B::C x) { return 2; }
}

void h() {
  A::g(B::C());
}

В пространстве имен A код объявляет несколько перегрузок функции f и шаблонную функцию g, которая вызывает f. Затем мы объявляем новый тип в пространстве имен B и перегружаем f для нового типа в пространстве имен A. Компиляция с g++ 4.2 дает

order.cpp: In function ‘int A::g(T) [with T = B::C]’:
order.cpp:21:   instantiated from here
order.cpp:7: error: no matching function for call to ‘f(B::C&)’
order.cpp:3: note: candidates are: int A::f(int)
order.cpp:4: note:                 int A::f(long int)

Код работает, если я выполняю одно из следующих действий:

  • Удалите пространства имен.
  • Переместить перегрузку f для B:: C в пространство имен B (благодаря поиску Koenig).
  • Переместить объявление B:: C и его перегрузку над определением g().

Я особенно озадачен (3), так как у меня создалось впечатление, что разрешение перегрузки должно быть независимым от порядка деклараций. Это ожидаемое поведение С++?

Ответ 1

Clang дает следующее сообщение об ошибке, которое дает некоторые подсказки:

$ clang -fsyntax-only test.cc -Wall
test.cc:7:10: error: call to function 'f' that is neither visible in the
      template definition nor found by argument-dependent lookup
  return f(x);
         ^
test.cc:21:3: note: in instantiation of function template specialization
      'A::g<B::C>' requested here
  A::g(B::C());
  ^
test.cc:17:5: note: 'f' should be declared prior to the call site or in
      namespace 'B'
int f(B::C x) { return 2; }
    ^
1 error generated.

В частности, вы столкнулись с детальностью двухфазного поиска зависимых имен в определениях шаблонов. В С++ 98 [temp.dep.candidate] говорит:

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

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

Так как A::f(B::C x) не найден с использованием связанных пространств имен (например, зависящий от аргументов поиск), он должен быть видимым на сайте определения шаблона, а не только в момент создания экземпляра.

Ответ 2

Например

int f(int x) { return 0; }
int f(long x) { return 1; }

функции не являются функциями шаблона (т.е. перед ними нет template <class T>. T - параметр шаблона.) Поэтому их можно скомпилировать "на лету", когда достигается шаблонный код.