Поиск, зависящий от аргументов (Argument-dependent lookup)
Поиск, зависящий от аргументов, также известный как 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) не является неквалифицированным
// идентификатором
}
Подробности
Во-первых, поиск, зависящий от аргументов, не рассматривается, если поисковый набор, созданный обычным неквалифицированным поиском, содержит любое из следующего:
В противном случае для каждого аргумента в выражении вызова функции проверяется его тип, чтобы определить связанный набор пространств имён и классов, который он добавит к поиску.
|
Если какое-либо пространство имён в связанном наборе классов и пространств имён является встроенным пространством имён, его включающее пространство имён также добавляется к набору. Если какое-либо пространство имён в связанном наборе классов и пространств имён непосредственно содержит встроенное пространство имён, это встроенное пространство имён добавляется к набору. |
(начиная с C++11) |
После определения связанного набора классов и пространств имён все объявления, найденные в классах этого набора, отбрасываются с целью дальнейшей обработки ADL, за исключением дружественных функций и шаблонов функций в области видимости пространства имён, как указано в пункте 2 ниже.
Набор объявлений, найденных обычным неквалифицированным поиском, и набор объявлений, найденных во всех элементах связанного набора, созданного ADL, объединяются согласно следующим специальным правилам
Примечание
Из-за поиска, зависящего от аргументов, функции, не являющиеся элементами, и операторы, не являющиеся элементами, определенные в том же пространстве имён, что и класс, считаются частью открытого интерфейса этого класса (если они обнаруживаются через 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 поиск (то есть поиск только в связанных пространствах имён):
|
(начиная с 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 | дважды вложенные классы не имели связанных пространств имён (включающие их классы не являются элементами какого-либо пространства имён) |
связанные пространства имён расширяются до самых внутренних объемлющих пространств имён |
Смотрите также
Внешние ссылки
|