Поиск, зависящий от аргументов
Поиск, зависящий от аргументов (ADL), также известный как поиск Кёнига[1], — это набор правил поиска неквалифицированных имён функций в выражениях вызова функции, включая неявные вызовы функций для перегруженных операторов. Эти имена функций ищутся в пространствах имён их аргументов в дополнение к областям видимости и пространствам имён, учитываемым при обычном поиске неквалифицированных имён.
Поиск, зависящий от аргументов, позволяет использовать операторы, определённые в другом пространстве имён. Пример:
#include <iostream>
int main()
{
std::cout << "Test\n"; // There is no operator<< in global namespace, but ADL
// examines std namespace because the left argument is in
// std and finds std::operator<<(std::ostream&, const char*)
operator<<(std::cout, "Test\n"); // Same, using function call notation
// However,
std::cout << endl; // Error: “endl” is not declared in this namespace.
// This is not a function call to endl(), so ADL does not apply
endl(std::cout); // OK: this is a function call: ADL examines std namespace
// because the argument of endl is in std, and finds std::endl
(endl)(std::cout); // Error: “endl” is not declared in this namespace.
// The sub-expression (endl) is not an unqualified-id
}
Подробности
Сначала поиск, зависящий от аргументов, не выполняется, если множество результатов обычного поиска неквалифицированных имён содержит что-либо из следующего:
В противном случае для каждого аргумента в выражении вызова функции исследуется его тип, чтобы определить связанное множество пространств имён и классов, которое будет добавлено к множеству поиска.
std::meta::info(начиная с C++26), связанное множество пространств имён и классов пусто. | Для | (начиная с C++26) |
T» или «указатель на массив T» исследуется тип T, и его связанное множество классов и пространств имён добавляется в множество.F класса X» исследуются типы параметров функции, возвращаемый тип и класс X, а их связанные множества классов и пространств имён добавляются в множество.T класса X» исследуются тип члена и тип X, а их связанные множества классов и пространств имён добавляются в множество.- Кроме того, если набор перегрузок задан идентификатором шаблона, исследуются все его аргументы шаблона-типов и аргументы шаблона-шаблона (но не аргументы шаблона-значения), а их связанные множества классов и пространств имён добавляются в множество.
| Если какое-либо пространство имён в связанном множестве классов и пространств имён является встроенным пространством имён, в множество также добавляется охватывающее его пространство имён. Если какое-либо пространство имён в связанном множестве классов и пространств имён непосредственно содержит встроенное пространство имён, это встроенное пространство имён добавляется в множество. | (начиная с 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 (если только типы элементов вектора/пары не являются пользовательскими типами, что добавило бы их пространство имён в ADL). Такие операторы не будут найдены при инстанцировании шаблонов, например алгоритмов стандартной библиотеки. Дополнительные сведения см. в разделе зависимые имена.
ADL может найти дружественную функцию (обычно перегруженный оператор), определённую целиком внутри класса или шаблона класса, даже если она никогда не объявлялась на уровне пространства имён.
template<typename T>
struct number
{
number(int);
friend number gcd(number x, number y) { return 0; }; // Definition within
// a class template
};
// Unless a matching declaration is provided gcd is
// an invisible (except through ADL) member of this namespace
void g()
{
number<double> a(3), b(4);
a = gcd(a, b); // Finds gcd because number<double> is an associated class,
// making gcd visible in its namespace (global scope)
// b = gcd(3, 4); // Error; gcd is not visible
}
| Хотя вызов функции может быть разрешён с помощью 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); // Syntax error until C++20 (unqualified lookup finds no f)
N1::f<3>(s); // OK, qualified lookup finds the template 'f'
N2::f<3>(s); // Error: N2::f does not take a non-type parameter
// N1::f is not looked up because ADL only works
// with unqualified names
using N2::f;
f<3>(s); // OK: Unqualified lookup now finds N2::f
// then ADL kicks in because this name is unqualified
// and finds 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); // Calls B::f (endless recursion)
}
void g(A::X x)
{
g(x); // Error: ambiguous between B::g (ordinary lookup)
// and A::g (argument-dependent lookup)
}
void h(A::Y y)
{
h(y); // Calls B::h (endless recursion): ADL examines the A namespace
// but finds no A::h, so only B::h from ordinary lookup is used
}
}
Отчёты об ошибках стандарта
Следующие отчёты об ошибках, изменяющие поведение, были применены задним числом к ранее опубликованным стандартам C++.
| DR | Применено к | Поведение согласно опубликованному тексту | Корректное поведение |
|---|---|---|---|
| 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 | для классов, вложенных дважды, не определялись связанные пространства имён (их охватывающие классы не являются членами какого-либо пространства имён) | связанные пространства имён расширены до наиболее внутренних охватывающих пространств имён |
| CWG 2857 | C++98 | связанные классы неполного классового типа включали его базовые классы | не включаются |
См. также
Внешние ссылки
|
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/adl