Поиск с учётом аргументов
Поиск с учётом аргументов, также известный как ADL (Argument-dependent lookup) или поиск Коэнига [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
}Подробности
Во-первых, поиск с учётом аргументов не рассматривается, если набор результатов обычного поиска неуточнённых имён содержит одно из следующих:
В противном случае для каждого аргумента в выражении вызова функции проверяется его тип, чтобы определить связанный набор пространств имён и классов, которые он добавит к поиску.
- Кроме того, если множество перегрузок названо с помощью идентификатора шаблона, проверяются все его аргументы-шаблоны типа и аргументы-шаблоны шаблона (но не аргументы-шаблоны нетипового типа), а связанные с ними классы и пространства имён добавляются в набор.
| Если любое пространство имён в связанном наборе классов и пространств имён является вложенным пространством имён, то его окружающее пространство имён также добавляется в набор. Если любое пространство имён в связанном наборе классов и пространств имён непосредственно содержит вложенное пространство имён, это вложенное пространство имён добавляется в набор. | (с 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) |
В следующих контекстах выполняется поиск только с учётом аргументов (т. е. поиск только в связанных пространствах имён):
| (с 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 не учитывается, кроме использования объявлений |
| CWG 997 | C++98 | зависимые типы параметров и возвращаемые типы были исключены из рассмотрения при определении связанных классов и пространств имён шаблона функции | включены |
| CWG 1690 | C++98 C++11 | ADL не мог найти лямбда-выражения (C++11) или объекты локальных типов классов (C++98), которые возвращаются | они могут быть найдены |
| CWG 1691 | C++11 | ADL имел неожиданное поведение для неявных объявлений перечислений | исправлено |
| CWG 1692 | C++98 | классы с двойным вложением не имели связанных пространств имён (их внешние классы не являются членами пространства имён) | связанные пространства имён расширены до самых внутренних внешних пространств имён |
См. также
Внешние ссылки
|
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/adl