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

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

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

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

Ответы [ 4 ]

197 голосов
/ 13 ноября 2011

Поиск Кенига или Аргумент-зависимый поиск описывает, как компилятор ищет в C ++ неквалифицированные имена.

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

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

Проще говоря, Николай Йозуттис заявляет 1 :

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

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

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

MyNamespace::MyClass obj; // global object


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

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

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

Алгоритм говорит компилятору не только смотреть на локальную область видимости, но также на пространства имен, которые содержат тип аргумента. Таким образом, в приведенном выше коде компилятор обнаруживает, что объект 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 ++: Учебное пособие и справочник .

60 голосов
/ 13 ноября 2011

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

. Именно из-за Koenig Lookup мы можем написать это:

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

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

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

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

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

28 голосов
/ 13 ноября 2011

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

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

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) в main().

20 голосов
/ 13 ноября 2011

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

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

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

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

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

...