Що таке "Пошук аргументів" (він же ADL або "Koenig Lookup")?


176

Які хороші пояснення щодо того, який пошук залежить від аргументів? Багато людей також називають його Koenig Lookup.

Переважно я хотів би знати:

  • Чому це добре?
  • Чому це погано?
  • Як це працює?




Відповіді:


223

Koenig Lookup або Argument Dependent Lookup описує, як некваліфіковані імена шукають компілятор у 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().

У чому перевага пошуку Koenig?

Як показано на прикладі простого коду, пошук Koenig забезпечує програмісту зручність і простоту використання. Без пошуку Koenig не було б накладних витрат на програміста, щоб неодноразово вказувати повністю кваліфіковані імена або замість цього використовувати численні usingдекларації.

Чому критика пошуку Кеніга?

Надмірна залежність від пошуку Koenig може призвести до смислових проблем, а програміст іноді застає програватися.

Розглянемо приклад std::swap, який є стандартним алгоритмом бібліотеки для заміни двох значень. Під час пошуку Koenig потрібно бути обережним при використанні цього алгоритму, оскільки:

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)було б неоднозначно.

Дрібниці:

Чому його називають "пошуком Koenig"?

Тому що його розробили колишній науковий співробітник AT&T та Bell Labs Ендрю Кеніг .

Подальше читання:


1 Визначення пошуку Koenig є таким, як визначено в книзі Йозуттіса, Стандартна бібліотека C ++: Навчальний посібник та довідник .


11
@AlokSave: +1 для відповіді, але дрібниці невірні. Кеніг не вигадав ADL, як він зізнається тут :)
legends2k

20
Приклад у критиці алгоритму Кеніга можна вважати "особливістю" пошуку Кеніга настільки ж, як "кон". Використання std :: swap () таким чином є загальною ідіомою: Надайте "за допомогою std :: swap ()" у випадку, якщо не передбачена більш спеціалізована версія A :: swap (). Якщо спеціалізована версія A :: свопу () є в наявності, ми Normall хочемо , що один буде називатися. Це забезпечує більшу загальність для swap () дзвінка, оскільки ми можемо довіряти дзвінку для компіляції та роботи, але ми також можемо довіряти більш спеціалізованій версії, яка буде використана, якщо вона є.
Ентоні Холл

6
@anthrond У цьому є більше. З цим std::swapви дійсно повинні це зробити, оскільки єдиною альтернативою було б додати std::swapчітку спеціалізацію функції шаблону для вашого Aкласу. Але якщо ваш Aклас є самим шаблоном, це буде часткова спеціалізація, а не явна спеціалізація. І часткова спеціалізація функції шаблону не допускається. Додавання перевантаження з боку std::swapбуло б альтернативою, але явно заборонено (ви не можете додавати речі до stdпростору імен). Тож ADL - єдиний спосіб std::swap.
Адам Бадура

1
Я б очікував побачити згадку про перевантажених операторах під "перевагою пошуку koenig". приклад з std::swap()здається трохи назад. Я б очікував, що проблема буде в тому випадку, коли std::swap()вибрано, а не перевантаження, специфічну для типу A::swap(),. Приклад з std::swap(A::MyClass&, A::MyClass&)здається оманливим. оскільки stdніколи не було б конкретної перевантаження для типу користувача, я не думаю, що це чудовий приклад.
Арвід

1
@gsamaras ... І? Всі ми можемо бачити, що функція ніколи не була визначена. Ваше повідомлення про помилку доводить, що воно спрацювало насправді, тому що воно шукає MyNamespace::doSomething, а не просто ::doSomething.
Фонд позову Моніки

69

Якщо в Koenig Lookup, якщо функція викликається без вказівки її простору імен, то ім'я функції також шукається у просторі імен, у яких визначено тип аргументів (аргументів). Ось чому він також відомий як аргументозалежне ім'я пошуку , коротше просто ADL .

Через Lookup Koenig ми можемо написати це:

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) Більш детальне вивчення залежної від аргументації пошуку.
Наваз

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), яке називалося таким чином, оскільки пошук залежить від аргументу (точніше, від типу аргументу). Оскільки схему винайшов Ендрю Р. Кеніг, її також часто називають пошуком Koenig.

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


Дякую! Ніколи реально не розумів, чому це було там!
user965369

20

Не все з цього приводу добре, на мою думку. Люди, включаючи постачальників компіляторів, ображали це через його іноді нещасну поведінку.

ADL несе відповідальність за капітальний ремонт петлі дальності в С ++ 11. Щоб зрозуміти, чому ADL іноді може мати ненавмисні ефекти, врахуйте, що враховуються не лише простори імен, де визначені аргументи, але й аргументи шаблонів аргументів, типів параметрів типів функцій / типів pointee типів покажчиків цих аргументів і так далі.

Приклад використання прискорення

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

Це призвело до неоднозначності, якщо користувач використовує бібліотеку boost.range, оскільки обидва std::beginзнаходять (за допомогою ADL std::vector) та boost::beginзнаходять (використовуючи ADL boost::shared_ptr).


Мене завжди цікавили, яка користь у першу чергу розглядати аргументи шаблону.
Dennis Zickefoose

Чи справедливо сказати, що ADL рекомендується використовувати лише операторам, і краще писати простори імен прямо для інших функцій?
балки

Чи він також враховує простори імен базових класів аргументів? (це було б шалено, якщо це, звичайно).
Алекс Б

3
як виправити? використовувати std :: почати?
Польма

2
@paulm Так, std::beginочищує двозначність простору імен.
Нікос
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.