Интересное поведение компилятора с пространствами имен

Предположим, что следующий код:

#include <iostream>
using namespace std;

namespace X
{
  class A{};

  void f(A a){}

  void g(int a){}
}

int main()
{
  X::A a;
  f(a);
  g(5);
}

При компиляции кода возникает следующая ошибка компиляции:

main.cpp: В функции 'int main()':
main.cpp: error: 'g' не был объявлен в этой области

Таким образом, функция f компилируется отлично, но g - нет. Как? Оба они принадлежат к одному пространству имен. Выводит ли компилятор, что функция f принадлежит пространству имен X из аргумента типа X::A? Как работает компилятор в таких случаях?

Ответ 1

Это работает для выражения вызова функции:

f(a);

поскольку пространство имен, к которому принадлежит X::A, включено в поиск функции f из-за зависимого от аргумента поиска (ADL), cppreference объясняет ADL следующим образом:

Аргумент-зависимый поиск, также известный как ADL или поиск Koenig, является набор правил поиска неквалифицированных имен функций в выражения функции-вызова, включая неявные вызовы функций для перегруженных операторов. Эти имена функций отображаются в пространства имен их аргументов в дополнение к областям и пространствам имен рассмотренный обычным безусловным поиском имен.

Аргумент-зависимый поиск позволяет использовать определенные операторы в другом пространстве имен

Это описано в черновик проекта С++ 3.4.2 Поиск по зависимым от аргументам имени:

Когда постфиксное выражение в вызове функции (5.2.2) является неквалифицированным-id, другие пространства имен не считаются во время обычного неквалифицированного поиска (3.4.1) можно искать и в этих пространствах имен пространство имен объявления друзей или функции (11.3), которые не отображаются в других местах, могут быть найдены

и далее:

Для каждого аргумента типа типа T в вызове функции существует набор нулевых или более связанных пространств имен и набор нулевых или более связанных классов. Определены наборы пространств имен и классов полностью по типам аргументов функции (и пространству имен любого аргумента шаблона шаблона).

и включает следующую марку:

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

а далее - аналогичный пример вашей проблемы:

namespace NS {
  class T { };
  void f(T);
  void g(T, int);
}

NS::T parm;
void g(NS::T, float);

int main() {
  f(parm); // OK: calls NS::f
  extern void g(NS::T, float);
  g(parm, 1); // OK: calls g(NS::T, float)
}

Выражение вызова функции:

g(5);

не работает, поскольку ADL не добавляет пространства имен для аргументов, которые являются фундаментальными типами.

Herb Sutter охватывает ADL в Gotw # 30 и в Что в Класс? - Принцип интерфейса.

Ответ 2

X::A a;
f(a);

работает из-за поиска, зависящего от аргументов (также известный как поиск Koenig). a является объектом класса a внутри пространства имен X, когда компилятор ищет подходящую функцию f, в этом случае он будет искать пространство имен X. Подробнее см. Поиск зависимых аргументов.

Ответ 3

Когда код f(a), компилятор находит функцию void f(A a){} в namespace X из-за ADL (аргумент зависимый поиск, также известный как поиск Koenig).

A объявляется в пространстве имен X, поэтому, когда компилятору необходимо найти определение f, он включает возможности из этого пространства имен, потому что объект A типа A находится в этом пространство имен (объявлено X::A a;).

С другой стороны, int не объявляется в namespace X, поэтому namespace X не включается в поиск. Поскольку соответствующая функция для f не найдена, она не компилируется.