Поиск Кёнига в языке C++ — это набор правил для поиска неквалифицированных (без оператора разрешения области видимости ::) имён функций и операторов. По-другому он называется поиском, зависящим от аргументов, по-английски — argument-dependent lookup (ADL), Koenig lookup.
ADL в упрощённой формулировке означает, что при поиске имени функции компилятор будет рассматривать не только то пространство имён, в котором она находится, но ещё и пространства имён, которые содержат типы аргументов этой функции. В отличие от перегрузки функций, ADL — особенность вызова функции, а не её объявления [Дьюхерст07, 96—97].
Герб Саттер пишет [Саттер05, 118]:
Попробуем разобраться. Далее представлен исчерпывающий пример [Саттер03, 243] [Дьюхерст07, 97]:
#include <iostream>
#include <string> // Здесь объявлена функция
int main()
{
std::string hello = "Hello, world";
std::cout << hello; // Вызывается std::operator<<
}
Если бы поиск Кёнига не работал, пришлось бы писать std::operator<<(std::cout, hello) (теряется смысл в перегрузке оператора) либо using std::operator<<, либо using namespace std.
Другой полезный пример использования ADL — прочие инфиксные операторы (типа x + x, что эквивалентно вызову operator+(x, x)) [Дьюхерст07, 97].
Есть пространство имён Cats, в нём класс Cat.
namespace Cats
{
class Cat
{
std::string name;
Breed breed;
int weight;
};
//
И функция pet() находится внутри пространства имён Cats. Погладим кота.
void pet(Cat& cat)
{
std::cout << __FUNCTION__ << std::endl;
}
В функции main() пишем следующие строчки. И… магия!
Cats::Cat cat;
pet(cat);
Для функции pet() явно не указано пространство имён. Но почему это работает? В функцию передаётся аргумент, пространство имён которого уже известно. По этому пространству имён ADL ищет функцию pet().
Добавим в пространство имён Cats структуру Food со вложенным перечислением и структурой.
struct Food
{
enum class Type
{
Meat,
Fish,
Milk,
VitaminsAndMinerals
} type;
struct NutritionFacts
{
int calories;
int protein;
int carbohydrates;
int fat;
} nutritionFacts;
};
Добавим оператор в это же пространство имён.
void operator+=(Cat& cat, Food& food)
{
std::cout << __FUNCTION__ << std::endl;
}
Попробуем использовать его. Покормим кота.
Cats::Food food;
cat += food;
Всё отработает без ошибок: food и cat оба относятся к одному пространству имён. ADL ищет оператор в нём. Это самый важный случай использования ADL.
ADL срабатывает и в том случае, когда аргумент имеет тип перечисления (enum).
void breedInfo(Breed breed)
{
std::cout << __FUNCTION__ << std::endl;
}
breedInfo(Cats::Breed::Birman); // Cats::breedInfo
То же справедливо для массивов, указателей (подобно ссылкам в примере выше), объединений (union), структур, классов, охватываемых другими классами.
void nutritionInfo(Food::NutritionFacts& nutritionFacts)
{
std::cout << __FUNCTION__ << std::endl;
}
Cats::Food::NutritionFacts nutritionFacts;
nutritionInfo(nutritionFacts); // Cats::nutritionInfo
В книге «Стандарты программирования на C++» Герб Саттер и Андрей Александреску [Саттер05, 118, 120, 136] дают три рекомендации, которые касаются ADL. В первой рекомендации они призывают использовать этот механизм.
В то же самое время авторы предостерегают от случайного эффекта.
namespace Cats
{
template <typename T>
int* operator+(T, unsigned)
{
std::cout << __FUNCTION__ << std::endl;
}
}
int main()
{
std::vector<Cats::Cat> cats(5);
cats[0];
}
Данная практика чревата появлением ошибок, которые трудно обнаружить. Почему? В стандартной библиотеке оператор [] может внутри содержать v.begin() + n, а может быть устроен по-другому, всё зависит от реализации. Реализация vector<T>::iterator может дойти до пространства имен Cats, а может и не дойти (то есть перегруженный оператор может быть вызван, а может и нет). В итоге получается непредсказуемый результат.
Авторы говорят [Саттер05, 121]: «Да, стандартная библиотека C++ помещает алгоритмы и другие шаблоны функций [...] в то же пространство имён, что и множество типов [...]. К счастью, теперь у нас больше опыта и мы знаем, как следует поступать. Не делайте так, как сделано в стандартной библиотеке».
По большей части поиск Кёнига работает незаметно и облегчает жизнь, но может привести к некоторым случайностям, поэтому полезно знать о деталях его работы.
В заключение нужно сказать, что имя Эндрю Кёнига не раз упоминается в книге создателя языка Бьерна Страуструпа «Дизайн и эволюция C++» [Страуструп06, 56, 92, 112, 136, 139, 155, 178, 215, 234, 236, 246, 251, 258, 260, 269, 309, 328, 333, 345, 349, 357, 373, 387, 390, 398, 400, 401, 404], он повлиял на многие аспекты языка и сам написал большое количество работ по языку C++.
Документация на сайте CppReference.com.
Приложение: исходный код.
[b][Саттер03][/b] Саттер Г. Решение сложных задач на C++. Серия C++ In-Depth, т. 4.: Пер. с англ. — М.: Издательский дом «Вильямс», 2003. — 400 с.: ил. Парал. тит. англ. ISBN 5-8459-0352-1
[b][Саттер05][/b] Саттер Г., Александреску А. Стандарты программирования на C++: Пер. с англ. — М.: Издательский дом «Вильямс», 2005. — 224 с.: ил. — Парал. тит. англ. ISBN 5-8459-0859-0
[b][Дьюхерст07][/b] Дьюхерст С. C++. Священные знания. — Пер. с англ. — СПб.: Символ-Плюс, 2007. — 240 с., ил. ISBN-13: 978-5-93286-095-3, ISBN-10: 5-93286-095-2
[b][Страуструп06][/b] Страуструп Б. Дизайн и эволюция C++: Пер. с англ. — М.: ДМК Пресс; СПб: Питер, 2006. — 448 с.: ил. ISBN 5-469-01217-4