Объясните механизм: почему функция, определённая как friend внутри шаблонного класса, обычно не находится обычным поиском имени, но вызывается через ADL?
Такая функция становится функцией пространства имён, но остаётся скрытой от обычного неквалифицированного поиска имени, если отдельное объявление в пространстве имён не сделано. ADL находит её по ассоциированному типу аргумента, поэтому вызов с объектами соответствующего шаблонного класса работает.
При инстанцировании шаблонного класса обычно создаётся отдельная функция для каждой специализации класса. Это позволяет связать операцию непосредственно с типом и не загрязнять обычный набор имён пространства имён.
Механизм скрытых friend-функций особенно полезен для операторов и операций, симметричных относительно аргументов. Например, сравнение двух объектов логически относится к типу объекта, но не обязано быть его методом.
Обычная функция пространства имён была бы видна всем клиентам через обычный поиск и могла бы увеличивать число конфликтующих перегрузок. Hidden friend сохраняет функцию рядом с типом, а поиск через ADL делает её доступной только в контексте подходящих аргументов.
Если функция определена внутри шаблонного класса как friend, разработчик может ожидать, что её можно вызвать с квалифицированным именем пространства имён. Однако без отдельного namespace-объявления такой вызов обычно не проходит обычный поиск.
Неверное понимание механизма приводит к ошибкам при проектировании операторов, функций сравнения и пользовательских точек расширения. Дополнительный риск возникает при повторном создании одинаковой friend-функции в разных специализациях: совпадающие сигнатуры могут привести к конфликту определений.
Friend-функция, определённая внутри класса, не становится членом класса. Она является функцией окружающего пространства имён, но её имя не вводится в область видимости для обычного поиска так, как имя обычной namespace-функции.
При вызове с аргументом типа Box<int> компилятор выполняет argument-dependent lookup. Ассоциированное пространство имён типа Box<int> рассматривается при ADL, и скрытая friend-функция находится среди функций, связанных с этим типом.
Минимальный пример:
Вызов equal(a, b) успешен благодаря ADL. При этом попытка вызвать lib::equal(a, b) может завершиться ошибкой, поскольку обычный квалифицированный поиск не видит скрытое имя без подходящего namespace-объявления.
Для каждой специализации класса friend-определение рассматривается в контексте этой специализации. Поэтому Box<int> и Box<double> обычно получают функции с разными параметрами. Если же разные специализации порождают одну и ту же сигнатуру функции, определения могут конфликтовать, особенно если тело не является согласованным единым определением.
Скрытая friend-функция также важна в шаблонном коде с зависимыми аргументами. На этапе определения шаблона обычный поиск и ADL могут выполняться в разные моменты, поэтому добавление перегрузки после определения шаблона не всегда влияет на вызов: нужно учитывать правила двухфазного поиска.
В библиотеке создаётся шаблонный тип Quantity<Unit>, для которого нужно поддержать симметричное сравнение двух величин. Возможны три решения.
Quantity<Unit>, не требует отдельного публичного имени и естественно поддерживает симметричный интерфейс.Для сравнения двух величин выбирают третий вариант. В результате вызов выглядит естественно, функция доступна именно для подходящих типов, а пространство имён библиотеки не получает лишнюю общедоступную перегрузку.
Нет. Friend-функция не является членом класса: у неё нет неявного параметра this, и вызвать её как объект.функция() нельзя. Она относится к окружающему пространству имён, но имя скрыто от обычного поиска и обычно обнаруживается ADL.
Нет. ADL работает только при наличии аргументов, чьи типы связывают вызов с нужным пространством имён или классом. Вызов без аргументов не может найти hidden friend через ADL. Аналогично, передача объекта через неподходящий тип-обёртку или вызов с полностью некорректными аргументами может исключить нужную функцию из поиска.
Разные специализации не гарантируют разные функции: важна итоговая сигнатура. Если две специализации порождают определения одной и той же namespace-функции, можно получить конфликт повторного определения или нарушение требований единого определения. Поэтому параметры friend-функции обычно делают зависимыми от параметров специализации либо выносят общее определение в отдельную функцию с контролируемой декларацией.