Каковы хорошие объяснения того, что поиск зависит от аргумента? Многие также называют это 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
очищает двусмысленность пространства имен.