Пространства имён
Варианты
Действия

Поиск, зависящий от аргументов (Argument-dependent lookup)

Материал из cppreference.com
 
 
 
Функции
Общее
Категории значений (lvalue, rvalue, xvalue)
Порядок оценки (sequence points)
Константные выражения
Потенциально оцениваемые выражения
Первичные выражения
Лямбда-выражения(C++11)
Литералы
Целочисленные литералы
Литералы с плавающей запятой
Логические литералы
Символьные литералы включая управляющие последовательности
Строковые литералы
Литерал нулевого указателя(C++11)
Пользовательский литерал(C++11)
Операторы
a=b, a+=b, a-=b, a*=b, a/=b, a%=b, a&=b, a|=b, a^=b, a<<=b, a>>=b
++a, --a, a++, a--
+a, -a, a+b, a-b, a*b, a/b, a%b, ~a, a&b, a|b, a^b, a<<b, a>>b
a||b, a&&b, !a
a==b, a!=b, a<b, a>b, a<=b, a>=b, a<=>b(C++20)
a[b], *a, &a, a->b, a.b, a->*b, a.*b
a(...), a,b, a?b:c
Выражение new
Выражение delete
Выражение throw
alignof
sizeof
sizeof...(C++11)
typeid
noexcept(C++11)
Выражения свёртки(C++17)
Альтернативные представления операторов
Приоритет и ассоциативность
Перегрузка операторов
Сравнение по умолчанию(C++20)
Преобразования
Неявные преобразования
const_cast
static_cast
reinterpret_cast
dynamic_cast
Явные преобразования: (T)a, T(a)
Пользовательское преобразование
 

Поиск, зависящий от аргументов, также известный как ADL, или поиск по Кенигу [1], представляет собой набор правил для поиска неквалифицированных имён функций в выражениях вызова функций, включая неявные вызовы функций для перегруженных операторов. Имена этих функций ищутся в пространствах имён их аргументов в дополнение к областям видимости и пространствам имён, рассматриваемым обычным неквалифицированным поиском имени.

Поиск, зависящий от аргументов, позволяет использовать операторы, определённые в другом пространстве имён. Пример:

#include <iostream>
int main()
{
    std::cout << "Тест\n"; // В глобальном пространстве имён нет operator<<, но ADL
                           // исследует пространство имён std, потому что левый аргумент
                           // находится в std,
                           // и находит std::operator<<(std::ostream&, const char*)
    operator<<(std::cout, "Тест\n"); // то же самое, с использованием нотации вызова
                                     // функции

    // однако,
    std::cout << endl; // Ошибка: 'endl' не объявлена в этом пространстве имён.
                       // Это не вызов функции endl(), поэтому ADL не применяется

    endl(std::cout); // OK: это вызов функции: ADL проверяет пространство имён std,
                     // потому что аргумент endl находится в std, и находит std::endl

    (endl)(std::cout); // Ошибка: 'endl' не объявлена в этом пространстве имён.
                       // Подвыражение (endl) не является неквалифицированным
                       // идентификатором
}

Подробности

Во-первых, поиск, зависящий от аргументов, не рассматривается, если поисковый набор, созданный обычным неквалифицированным поиском, содержит любое из следующего:

1) объявление элемента класса
2) объявление функции в области видимости блока (это не using-объявление)
3) любое объявление, которое не является функцией или шаблоном функции (например, объект функция или другая переменная, имя которой конфликтует с именем функции, которая просматривается)

В противном случае для каждого аргумента в выражении вызова функции проверяется его тип, чтобы определить связанный набор пространств имён и классов, который он добавит к поиску.

1) Для аргументов фундаментальных типов связанный набор пространств имён и классов пуст
2) Для аргументов типа класса (включая объединение) набор состоит из
a) Сам класс
b) Все его прямые и косвенные базовые классы
c) Если класс является элементом другого класса, класс, элементом которого он является
d) Самые внутренние включающие пространства имён классов, добавленных к набору
3) Для аргументов, тип которых является специализацией шаблона класса, в дополнение к правилам класса, исследуются следующие типы, и связанные с ними классы и пространства имён добавляются к набору
a) Типы всех аргументов шаблона, предоставленные для параметров шаблона типов (пропускаются параметры шаблона, не являющихся типом, и шаблонные параметры шаблона)
b) Пространства имён, элементами которых являются любые шаблонные аргументы шаблона
c) Классы, элементами которых являются любые шаблонные аргументы шаблона (если они являются шаблонами-элементами класса)
4) Для аргументов перечислимого типа к набору добавляется самое внутреннее включающее пространство имён объявления перечисляемого типа. Если тип перечисления является элементом класса, этот класс добавляется в набор.
5) Для аргументов типа указателя на T или указателя на массив элементов типа T проверяется тип T, и связанный с ним набор классов и пространств имён добавляется к набору.
6) Для аргументов типа функции проверяются типы параметров функции и тип возвращаемого значения функции, и связанный с ними набор классов и пространств имён добавляется к набору.
7) Для аргументов типа указателя на функцию-элемеет F класса X проверяются типы параметров функции, тип возвращаемого значения функции и класс X, и к набору добавляется связанный с ними набор классов и пространств имён.
8) Для аргументов типа указателя на элемент данных T класса X проверяются и тип элемента, и тип X, и к набору добавляется связанный с ними набор классов и пространств имён.
9) Если аргументом является имя или адрес-выражения для набора перегруженных функций (или шаблонов функций), проверяется каждая функция в наборе перегрузки и в набор добавляется связанный с ней набор классов и пространств имён.
a) Кроме того, если набор перегрузок именован с помощью идентификатора шаблона, проверяются все аргументы шаблона типы и шаблонные аргументы шаблона (но не аргументы шаблона, не являющиеся типом), и связанный с ними набор классов и пространств имён добавляется к набору.

Если какое-либо пространство имён в связанном наборе классов и пространств имён является встроенным пространством имён, его включающее пространство имён также добавляется к набору.

Если какое-либо пространство имён в связанном наборе классов и пространств имён непосредственно содержит встроенное пространство имён, это встроенное пространство имён добавляется к набору.

(начиная с C++11)

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

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

1) директивы using в связанных пространствах имён игнорируются
2) дружественные функции в области видимости пространства имён (и шаблоны функций), объявленные в связанном классе, видны через ADL, даже если они не видны при обычном поиске
3) игнорируются все имена, кроме функций и шаблонов функций (нет коллизии с переменными)

Примечание

Из-за поиска, зависящего от аргументов, функции, не являющиеся элементами, и операторы, не являющиеся элементами, определенные в том же пространстве имён, что и класс, считаются частью открытого интерфейса этого класса (если они обнаруживаются через ADL) [2].

ADL является причиной установленной идиомы для обмена двух объектов в общем коде:

using std::swap;
swap(obj1, obj2);

поскольку при прямом вызове std::swap(obj1, obj2) не будут учитываться определяемые пользователем функции swap(), которые могут быть определены в том же пространстве имён, что и типы obj1 или obj2, а просто вызов неквалифицированной swap(obj1, obj2) ничего не вызовет, если не будет предоставлена определяемая пользователем перегрузка. В частности, std::iter_swap и все другие алгоритмы стандартной библиотеки используют этот подход при работе с типами Swappable.

Правила поиска по имени делают непрактичным объявление операторов в глобальном или определяемом пользователем пространстве имён, которые работают с типами из пространства имён std, например пользовательский operator>> или operator+ или std::vector или для std::pair (если только типы элементов vector/pair это определяемые пользователем типы, которые добавят свое пространство имён в ADL). Такие операторы не могут быть найдены из экземпляров шаблонов, таких как стандартные библиотечные алгоритмы. Смотрите зависимые имена для получения дополнительной информации.

ADL может найти дружественную функцию (обычно перегруженный оператор), которая полностью определена в классе или шаблоне класса, даже если она никогда не объявлялась на уровне пространства имён.

template<typename T>
struct number
{
    number(int);
    friend number gcd(number x, number y) { return 0; }; // определение в
                                                         // шаблоне класса
};
// если не предоставлено соответствующее объявление, gcd является невидимым
// (кроме как через ADL) элементом этого пространства имён
void g() {
    number<double> a(3), b(4);
    a = gcd(a,b); // находит gcd, потому что number<double> является ассоциированным
                  // классом, делающим gcd видимой в её пространстве имён (глобальная
                  // область видимости)
//  b = gcd(3,4); // Ошибка; gcd не видна
}

Хотя вызов функции может быть разрешён через ADL, даже если обычный поиск ничего не находит, вызов шаблона функции с явно указанными аргументами шаблона требует, чтобы было объявление шаблона, найденного обычным поиском (в противном случае, обнаружение неизвестного имени, за которым следует символ меньше, является синтаксической ошибкой)

namespace N1 {
  struct S {};
  template<int X> void f(S);
}
namespace N2 {
  template<class T> void f(T t);
}
void g(N1::S s) {
  f<3>(s);      // Синтаксическая ошибка до C++20 (неквалифицированный поиск не находит f)
  N1::f<3>(s);  // OK, квалифицированный поиск находит шаблон 'f'
  N2::f<3>(s);  // Ошибка: N2::f не принимает параметр, не являющийся типом
                //         N1::f не обнаруживается, потому что ADL работает только с
                //               неквалифицированными именами
  using N2::f;
  f<3>(s); // OK: Теперь неквалифицированный поиск находит N2::f,
           //     затем запускается ADL, потому что это имя неквалифицировано,
           //     и находит N1::f
}
(до C++20)

В следующих контекстах выполняется только ADL поиск (то есть поиск только в связанных пространствах имён):

  • поиск функций begin и end, не являющихся элементами, выполняется диапазонным циклом for, если поиск функций-элементов не удался
(начиная с C++11)
(начиная с C++17)

Пример

Пример из http://www.gotw.ca/gotw/030.htm

namespace A {
      struct X;
      struct Y;
      void f(int);
      void g(X);
}

namespace B {
    void f(int i) {
        f(i);   // вызывает B::f (бесконечная рекурсия)
    }
    void g(A::X x) {
        g(x);   // Ошибка: неоднозначность между B::g (обычный поиск)
                //         и A::g (поиск, зависящий от аргументов)
    }
    void h(A::Y y) {
        h(y);   // вызывает B::h (бесконечная рекурсия): ADL исследует пространство имён
                // A, но не находит A::h, поэтому используется только B::h из обычного
                // поиска
    }
}

Отчеты о дефектах

Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:

Номер Применён Поведение в стандарте Корректное поведение
CWG 33 C++98 связанные пространства имён или классы не указываются,
если аргумент, используемый для поиска, является адресом
группы перегруженных функций или шаблоном функции
указываются
CWG 90 C++98 ассоциированные классы вложенного класса не объединения
не включали включающий его класс, но вложенное объединение
было связано с включающим его классом
не объединения также связаны
CWG 239 C++98 объявление блочной области видимости функции, найденное
обычным неквалифицированным поиском, не предотвращало
выполнение ADL
ADL не рассматривается,
за исключением using объявлений
CWG 997 C++98 зависимые типы параметров и возвращаемые типы были
исключены из рассмотрения при определении связанных классов
и пространств имён шаблона функции
включены
CWG 1690 C++98
C++11
ADL не мог найти возвращаемые лямбда-выражения (C++11) или
объекты локальных классовых типов (C++98)
они могут быть найдена
CWG 1691 C++11 у ADL было неожиданное поведение для непрозрачных
объявлений перечисления
исправлено
CWG 1692 C++98 дважды вложенные классы не имели связанных пространств
имён (включающие их классы не являются элементами
какого-либо пространства имён)
связанные пространства имён расширяются до
самых внутренних объемлющих пространств
имён

Смотрите также

Внешние ссылки