Каковы хорошие объяснения того, что поиск зависит от аргумента? Многие также называют это Koenig Lookup.
Желательно, чтобы я знал:
- Почему это хорошо?
- Почему это плохо?
- Как это работает?
std::cout << "Hello world";не будет компилироваться
Каковы хорошие объяснения того, что поиск зависит от аргумента? Многие также называют это Koenig Lookup.
Желательно, чтобы я знал:
std::cout << "Hello world";не будет компилироваться
Ответы:
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 Эндрю Кенигом .
Стандарт C ++ 03/11 [basic.lookup.argdep]: 3.4.2 Поиск имени в зависимости от аргумента.
1 Определение поиска Кенига определено в книге Джозуттиса « Стандартная библиотека C ++: учебное пособие и справочник» .
std::swapвами на самом деле нужно это сделать, поскольку единственной альтернативой было бы добавить std::swapявную специализацию функции шаблона для вашего Aкласса. Тем не менее, если ваш Aкласс является самим шаблоном, это будет частичная специализация, а не явная специализация. И частичная специализация шаблонной функции не допускается. Добавление перегрузки std::swapбыло бы альтернативой, но явно запрещено (вы не можете добавлять вещи в stdпространство имен). Так что ADL - единственный путь std::swap.
std::swap()немного задом наперед. Я ожидаю, что проблема будет, когда std::swap()выбран, а не перегрузка, специфичная для типа A::swap(). Пример с std::swap(A::MyClass&, A::MyClass&)представлением вводит в заблуждение. так stdкак никогда не было бы определенной перегрузки для пользовательского типа, я не думаю, что это отличный пример.
MyNamespace::doSomething, а не только ::doSomething.
В Koenig Lookup, если функция вызывается без указания ее пространства имен, тогда имя функции также ищется в пространстве (ах) имен, в котором определен тип аргумента (ов). Вот почему оно также известно как Аргумент-зависимое имя Lookup , короче говоря просто ADL .
Именно из-за поиска Кенига мы можем написать это:
std::cout << "Hello World!" << "\n";
В противном случае мы должны были бы написать:
std::operator<<(std::operator<<(std::cout, "Hello World!"), "\n");
что на самом деле слишком много печатать, и код выглядит действительно ужасно!
Другими словами, в отсутствие Koenig Lookup даже программа Hello World выглядит сложной.
std::coutэто один аргумент функции, которого достаточно для включения ADL. Вы это заметили?
ostream<<(как в том, что он принимает в качестве аргументов, так и в том, что он возвращает). 2) Полностью квалифицированные имена (например, std::vectorили std::operator<<). 3) Более подробное исследование Argument Dependent Lookup.
std::endlкачестве аргумента, на самом деле является функцией-членом. В любом случае, если я использую "\n"вместо std::endl, то мой ответ правильный. Спасибо за комментарий.
f(a,b)вызывает свободную функцию. Так что в случае std::operator<<(std::cout, std::endl);, нет такой свободной функции, которая принимает в std::endlкачестве второго аргумента. Это функция-член, которая принимает в std::endlкачестве аргумента и для которой вы должны написать std::cout.operator<<(std::endl);. и так как есть свободная функция, которая принимает в char const*качестве второго аргумента, "\n"работает; '\n'будет работать так же.
Может быть, лучше начать с «почему», и только потом перейти к «как».
Когда были введены пространства имен, идея состояла в том, чтобы все было определено в пространствах имен, чтобы отдельные библиотеки не мешали друг другу. Однако это привело к проблеме с операторами. Посмотрите, например, на следующий код:
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().
Не все в этом хорошо, на мой взгляд. Люди, включая поставщиков компиляторов, оскорбляли его из-за его иногда неудачного поведения.
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).
std::beginочищает двусмысленность пространства имен.