Что такое «Аргумент-зависимый поиск» (он же ADL или «Поиск Кенига»)?

177

Каковы хорошие объяснения того, что поиск зависит от аргумента? Многие также называют это Koenig Lookup.

Желательно, чтобы я знал:

  • Почему это хорошо?
  • Почему это плохо?
  • Как это работает?
user965369
источник

Ответы:

224

Koenig Lookup или Argument Dependent Lookup описывает, как неквалифицированные имена ищутся компилятором в C ++.

Стандарт C ++ 11 § 3.4.2 / 1 гласит:

Когда postfix-выражение в вызове функции (5.2.2) является безусловным идентификатором, могут быть найдены другие пространства имен, не учитываемые во время обычного неквалифицированного поиска (3.4.1), и в этих пространствах имен объявления функций друзей области видимости области пространства ( 11.3) может быть найдено не видимое иное. Эти модификации поиска зависят от типов аргументов (и для аргументов шаблона шаблона - пространства имен аргумента шаблона).

В более простых сроках Николай Josuttis заявляет 1 :

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

Простой пример кода:

namespace MyNamespace
{
    class MyClass {};
    void doSomething(MyClass);
}

MyNamespace::MyClass obj; // global object


int main()
{
    doSomething(obj); // Works Fine - MyNamespace::doSomething() is called.
}

В приведенном выше примере нет ни using-declaration, ни using-directive, но все же компилятор правильно идентифицирует неквалифицированное имя doSomething()как функцию, объявленную в пространстве имен MyNamespace, применяя поиск Кенига .

Как это работает?

Алгоритм говорит компилятору не только смотреть на локальную область видимости, но также на пространства имен, которые содержат тип аргумента. Таким образом, в приведенном выше коде компилятор обнаруживает, что объект obj, являющийся аргументом функции doSomething(), принадлежит пространству имен MyNamespace. Таким образом, он смотрит на это пространство имен, чтобы найти объявление doSomething().

В чем преимущество поиска Кенига?

Как показывает приведенный выше пример простого кода, поиск по Кенигу обеспечивает удобство и простоту использования для программиста. Без поиска Кенига программисту потребовалось бы много времени, чтобы многократно указывать полностью определенные имена или вместо этого использовать многочисленные using-декларации.

Почему критика поиска Кенига?

Чрезмерная зависимость от поиска Кенига может привести к семантическим проблемам и иногда застать программиста врасплох.

Рассмотрим пример std::swap, представляющий собой стандартный алгоритм библиотеки для обмена двумя значениями. С поиском Кенига нужно было бы быть осторожным при использовании этого алгоритма, потому что:

std::swap(obj1,obj2);

может не показывать такое же поведение как:

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

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

Если существует пространство имен , Aи если A::obj1, A::obj2и A::swap()существует , то второй пример приведет к вызову A::swap(), который не может быть то , что хочет пользователь.

Кроме того, если по какой-либо причине оба A::swap(A::MyClass&, A::MyClass&)и std::swap(A::MyClass&, A::MyClass&)определены, то первый пример будет вызываться, std::swap(A::MyClass&, A::MyClass&)а второй не будет компилироваться, поскольку swap(obj1, obj2)будет неоднозначным.

Общая информация:

Почему это называется «поиск Кенига»?

Потому что он был разработан бывшим исследователем и программистом AT & T и Bell Labs Эндрю Кенигом .

Дальнейшее чтение:


1 Определение поиска Кенига определено в книге Джозуттиса « Стандартная библиотека C ++: учебное пособие и справочник» .

Alok Save
источник
11
@AlokSave: +1 за ответ, но пустяки не верны. Кениг не изобрел ADL, как он здесь признается :)
legends2k
20
Пример критики алгоритма Кенига можно рассматривать как «особенность» поиска Кенига, так же как и «против». Использование std :: swap () таким способом является обычной идиомой: предоставьте 'использование std :: swap ()' в случае, если не предусмотрена более специализированная версия A :: swap (). Если доступна специализированная версия A :: swap (), мы бы хотели , чтобы она вызывалась . Это обеспечивает большую универсальность для вызова swap (), поскольку мы можем доверять вызову для компиляции и работы, но мы также можем доверять более специализированной версии, которая будет использоваться, если она есть.
Энтони Холл
6
@anthrond Есть еще в этом. С std::swapвами на самом деле нужно это сделать, поскольку единственной альтернативой было бы добавить std::swapявную специализацию функции шаблона для вашего Aкласса. Тем не менее, если ваш Aкласс является самим шаблоном, это будет частичная специализация, а не явная специализация. И частичная специализация шаблонной функции не допускается. Добавление перегрузки std::swapбыло бы альтернативой, но явно запрещено (вы не можете добавлять вещи в stdпространство имен). Так что ADL - единственный путь std::swap.
Адам Бадура
1
Я бы ожидал увидеть упоминание о перегруженных операторах под «преимуществом поиска по Кенигу». пример с std::swap()немного задом наперед. Я ожидаю, что проблема будет, когда std::swap()выбран, а не перегрузка, специфичная для типа A::swap(). Пример с std::swap(A::MyClass&, A::MyClass&)представлением вводит в заблуждение. так stdкак никогда не было бы определенной перегрузки для пользовательского типа, я не думаю, что это отличный пример.
Арвид
1
@gsamaras ... А? Мы все видим, что функция никогда не была определена. Ваше сообщение об ошибке доказывает, что это сработало, на самом деле, потому что оно ищет MyNamespace::doSomething, а не только ::doSomething.
Фонд Моника иск
70

В Koenig Lookup, если функция вызывается без указания ее пространства имен, тогда имя функции также ищется в пространстве (ах) имен, в котором определен тип аргумента (ов). Вот почему оно также известно как Аргумент-зависимое имя Lookup , короче говоря просто ADL .

Именно из-за поиска Кенига мы можем написать это:

std::cout << "Hello World!" << "\n";

В противном случае мы должны были бы написать:

std::operator<<(std::operator<<(std::cout, "Hello World!"), "\n");

что на самом деле слишком много печатать, и код выглядит действительно ужасно!

Другими словами, в отсутствие Koenig Lookup даже программа Hello World выглядит сложной.

Наваз
источник
12
Убедительный пример.
Энтони Холл
10
@AdamBadura: обратите внимание, что std::coutэто один аргумент функции, которого достаточно для включения ADL. Вы это заметили?
Наваз
1
@meet: Ваш вопрос нуждается в длинном ответе, который не может быть предоставлен в этом месте. Поэтому я могу только посоветовать вам прочитать такие темы, как: 1) подпись ostream<<(как в том, что он принимает в качестве аргументов, так и в том, что он возвращает). 2) Полностью квалифицированные имена (например, std::vectorили std::operator<<). 3) Более подробное исследование Argument Dependent Lookup.
Наваз
2
@WorldSEnder: Да, вы правы. Функция, которая может принимать в std::endlкачестве аргумента, на самом деле является функцией-членом. В любом случае, если я использую "\n"вместо std::endl, то мой ответ правильный. Спасибо за комментарий.
Наваз
2
@Destructor: потому что вызов функции в форме f(a,b)вызывает свободную функцию. Так что в случае std::operator<<(std::cout, std::endl);, нет такой свободной функции, которая принимает в std::endlкачестве второго аргумента. Это функция-член, которая принимает в std::endlкачестве аргумента и для которой вы должны написать std::cout.operator<<(std::endl);. и так как есть свободная функция, которая принимает в char const*качестве второго аргумента, "\n"работает; '\n'будет работать так же.
Наваз
30

Может быть, лучше начать с «почему», и только потом перейти к «как».

Когда были введены пространства имен, идея состояла в том, чтобы все было определено в пространствах имен, чтобы отдельные библиотеки не мешали друг другу. Однако это привело к проблеме с операторами. Посмотрите, например, на следующий код:

namespace N
{
  class X {};
  void f(X);
  X& operator++(X&);
}

int main()
{
  // define an object of type X
  N::X x;

  // apply f to it
  N::f(x);

  // apply operator++ to it
  ???
}

Конечно, вы могли бы написать N::operator++(x), но это победило бы весь смысл перегрузки оператора. Следовательно, нужно было найти решение, которое позволило бы найти компилятор, operator++(X&)несмотря на то, что оно не входило в область видимости. С другой стороны, он по-прежнему не должен находить другое, operator++определенное в другом, несвязанном пространстве имен, что может сделать вызов неоднозначным (в этом простом примере вы не получите двусмысленности, но в более сложных примерах вы можете). Решением был Аргумент-зависимый поиск (ADL), названный таким образом, поскольку поиск зависит от аргумента (точнее, от типа аргумента). Поскольку схема была изобретена Эндрю Р. Кенигом, ее также часто называют поиском Кенига.

Хитрость заключается в том, что для вызовов функций, в дополнение к обычному поиску имен (который находит имена в области действия в момент использования), выполняется второй поиск в областях типов любых аргументов, переданных функции. Таким образом , в приведенном выше примере, если вы пишете x++в основном, он ищет operator++не только в глобальном масштабе, но , кроме того , в сфере , где тип x, N::Xбыл определен, то есть в namespace N. И там он находит соответствие operator++, и поэтому x++просто работает. Однако, другое, operator++определенное в другом пространстве имен N2, не будет найдено. Поскольку ADL не ограничен пространствами имен, вы также можете использовать f(x)вместо N::f(x)in main().

celtschk
источник
Спасибо! Никогда не понимал, почему это было там!
user965369
20

Не все в этом хорошо, на мой взгляд. Люди, включая поставщиков компиляторов, оскорбляли его из-за его иногда неудачного поведения.

ADL отвечает за капитальный ремонт цикла for-range в C ++ 11. Чтобы понять, почему ADL иногда может иметь непреднамеренные эффекты, рассмотрим, что учитываются не только пространства имен, в которых определены аргументы, но также аргументы шаблонных аргументов аргументов, типов параметров типов функций / типов-указателей типов указателей этих аргументов. и так далее и тому подобное.

Пример использования наддува

std::vector<boost::shared_ptr<int>> v;
auto x = begin(v);

Это привело к неоднозначности, если пользователь использует библиотеку boost.range, потому что оба std::beginнайдены (с помощью ADL std::vector) и boost::beginнайдены (с помощью ADL boost::shared_ptr).

Йоханнес Шауб - Литб
источник
Я всегда задавался вопросом, какая польза от рассмотрения аргументов шаблона в первую очередь.
Деннис Зикефуз
Справедливо ли говорить, что ADL рекомендуется только для операторов, и лучше писать пространства имен явно для других функций?
Балки
Учитывает ли он также пространства имен базовых классов аргументов? (это было бы безумием, если это так, конечно).
Алекс Б
3
как исправить? использовать std :: begin?
Пол
2
@paulm Да, std::beginочищает двусмысленность пространства имен.
Никос