В языке программирования C++ , зависимый от аргументов поиск ( ADL ) или зависимый от аргументов поиск имени [ 1] применяется к поиску неквалифицированного имени функции в зависимости от типов аргументов , переданных вызову функции . Это поведение также известно как поиск по Кенигу , поскольку его часто приписывают Эндрю Кенигу , хотя он не является его изобретателем. [2]
Во время поиска, зависящего от аргументов, могут быть найдены другие пространства имен, не рассматриваемые во время обычного поиска, где набор пространств имен для поиска зависит от типов аргументов функции. В частности, набор объявлений, обнаруженных во время процесса ADL и рассматриваемых для разрешения имени функции, представляет собой объединение объявлений, найденных обычным поиском, с объявлениями, найденными при поиске в наборе пространств имен, связанных с типами аргументов функции.
Пример ADL выглядит так:
пространство имен NS { класс А {}; void f ( A & a , int i ) {} } // пространство имен NS int main () { NS :: A a ; f ( a , 0 ); // Вызывает NS::f. }
Даже несмотря на то, чтоосновнойфункция не находится в пространстве имен NS, и пространство имен NS не находится в области действия, функцияNS::f(A&, целое)обнаруживается из-за объявленных типов фактических аргументов в операторе вызова функции.
Распространенный шаблон в стандартной библиотеке C++ — объявлять перегруженные операторы, которые будут найдены таким образом. Например, эта простая программа Hello World не скомпилировалась бы, если бы не ADL:
#include <iostream> #include <string> int main () { std :: string str = "привет мир" ; std :: cout << str ; }
Использование <<
эквивалентно вызову operator<<
без std::
квалификатора. Однако в этом случае перегрузка оператора <<, которая работает для , string
находится в std
пространстве имен, поэтому для ее использования требуется ADL.
Следующий код будет работать без ADL (который к нему в любом случае применяется):
#include <iostream> int main () { std :: cout << 5 ; }
Это работает, потому что оператор вывода для целых чисел является функцией-членом класса std::ostream
, который является типом cout
. Таким образом, компилятор интерпретирует это выражение как
std :: cout . оператор << ( 5 );
которые он может разрешить во время обычного поиска. Однако учтите, что, например, const char *
перегруженная функция operator<<
не является функцией-членом в std
пространстве имен и, таким образом, требует ADL для корректного поиска:
/* выведет предоставленную строку символов, как и ожидалось, с помощью ADL, полученного из типа аргумента std::cout */ оператор << ( std :: cout , "Привет" ) /* вызывает функцию-член ostream оператора <<, принимая void const*, которая выведет адрес предоставленной строки символов вместо содержимого строки символов */ std :: cout . Operator << ( "Привет" )
Другим примером является перегруженная функция , std
не являющаяся членом пространства имен, operator<<
для обработки строк:
/*эквивалентно оператору <<(std::cout, str). Компилятор выполняет поиск в пространстве имен std с помощью ADL из-за типа std::string параметра str и std::cout */ std :: cout << str ;
Как отмечает Кёниг в личной заметке [2] , без ADL компилятор выдал бы ошибку, сообщающую о том, что он не может найти operator<<
, поскольку в операторе явно не указано, что он найден в std
пространстве имен.
Функции, найденные ADL, считаются частью интерфейса класса. В стандартной библиотеке C++ несколько алгоритмов используют неквалифицированные вызовы swap
из std
пространства имен. В результате std::swap
используется общая функция, если ничего другого не найдено, но если эти алгоритмы используются со сторонним классом, Foo
, найденным в другом пространстве имен, которое также содержит , будет использоваться swap(Foo&, Foo&)
эта перегрузка .swap
Хотя ADL делает практичным для функций, определенных вне класса, вести себя так, как будто они являются частью интерфейса этого класса, он делает пространства имен менее строгими и поэтому может потребовать использования полностью определенных имен, когда они в противном случае не были бы нужны. Например, стандартная библиотека C++ широко использует неопределенные вызовы для std::swap
обмена двух значений. Идея заключается в том, что затем можно определить собственную версию swap
в собственном пространстве имен, и она будет использоваться в алгоритмах стандартной библиотеки. Другими словами, поведение
пространство имен N { структура А {}; } // пространство имен N А а ; А б ; std :: swap ( a , b );
может быть или не быть таким же, как поведение
использование std :: swap ; swap ( a , b );
(где a
и b
имеют тип N::A
), потому что если N::swap(N::A&, N::A&)
существует, то второй из приведенных выше примеров вызовет его, а первый — нет. Более того, если по какой-то причине определены оба N::swap(N::A&, N::A&)
и , то первый пример вызовет , но второй не скомпилируется, потому что это будет неоднозначно.std::swap(N::A&, N::A&)
std::swap(N::A&, N::A&)
swap(a, b)
В общем, чрезмерная зависимость от ADL может привести к семантическим проблемам. Если одна библиотека, L1
, ожидает, что неквалифицированные вызовы будут foo(T)
иметь одно значение, а другая библиотека, L2
ожидает, что будет иметь другое, то пространства имен теряют свою полезность. Если же L1
ожидает , L1::foo(T)
что будет иметь одно значение и L2
делает то же самое, то конфликта нет, но вызовы foo(T)
должны быть полностью квалифицированными (т. е. L1::foo(x)
в отличие от using L1::foo; foo(x);
), чтобы ADL не мешал.