Namespaces
Variants

Argument-dependent lookup

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Поиск, зависимый от аргументов (ADL), также известный как поиск Кёнига [1] , представляет собой набор правил для поиска неквалифицированных имён функций в выражениях вызова функций , включая неявные вызовы функций для перегруженных операторов . Эти имена функций ищутся в пространствах имён их аргументов в дополнение к областям видимости и пространствам имён, рассматриваемым обычным неквалифицированным поиском имён .

Поиск, зависимый от аргументов (argument-dependent lookup), позволяет использовать операторы, определенные в другом пространстве имен. Пример:

#include <iostream>
int main()
{
    std::cout << "Test\n"; // В глобальном пространстве имен нет operator<<, но ADL
                           // проверяет пространство имен std, потому что левый аргумент находится
                           // в std, и находит std::operator<<(std::ostream&, const char*)
    operator<<(std::cout, "Test\n"); // То же самое, с использованием синтаксиса вызова функции
    // Однако,
    std::cout << endl; // Ошибка: “endl” не объявлен в этом пространстве имен.
                       // Это не вызов функции endl(), поэтому ADL не применяется
    endl(std::cout); // OK: это вызов функции: ADL проверяет пространство имен std
                     // потому что аргумент endl находится в std, и находит std::endl
    (endl)(std::cout); // Ошибка: “endl” не объявлен в этом пространстве имен.
                       // Подвыражение (endl) не является неквалифицированным идентификатором
}

Содержание

Детали

Во-первых, поиск, зависимый от аргументов, не рассматривается, если набор поиска, полученный обычным unqualified lookup содержит любой из следующих элементов:

1) объявление члена класса.
2) объявление функции на уровне блока (которое не является using declaration ).
3) любое объявление, которое не является функцией или шаблоном функции (например, функциональный объект или другая переменная, чьё имя конфликтует с именем функции, которая ищется).

В противном случае, для каждого аргумента в выражении вызова функции его тип анализируется, чтобы определить ассоциированный набор пространств имён и классов , который будет добавлен к поиску.

1) Для аргументов фундаментального типа связанный набор пространств имён и классов является пустым.
2) Для аргументов типа класса (включая объединения), набор состоит из:
a) Сам класс.
b) Если класс является полным , все его прямые и косвенные базовые классы.
c) Если класс является членом другого класса , класс, членом которого он является.
d) Самые внутренние охватывающие пространства имён классов, добавленных в набор.
3) Для аргументов, тип которых является class template специализацией, в дополнение к правилам для классов, следующие ассоциированные классы и пространства имён добавляются в набор.
a) Типы всех аргументов шаблонов, предоставленных для параметров шаблонов типа (пропуская параметры шаблонов констант и пропуская параметры шаблонов шаблонов).
b) Пространства имен, в которых находятся любые аргументы шаблонов-шаблонов.
c) Классы, в которых любые шаблонные параметры-шаблоны являются членами (если они оказываются шаблонами-членами класса).
4) Для аргументов типа перечисления, в набор добавляется самая внутренняя охватывающая пространство имен, в котором объявлен тип перечисления. Если тип перечисления является членом класса, этот класс добавляется в набор.
5) Для аргументов типа указатель на T или указатель на массив T , тип T анализируется, и его ассоциированный набор классов и пространств имён добавляется в набор.
6) Для аргументов функционального типа проверяются типы параметров функции и тип возвращаемого значения, а их связанные наборы классов и пространств имён добавляются в множество.
7) Для аргументов типа указатель на член-функцию F класса X , типы параметров функции, тип возвращаемого значения функции и класс X анализируются, и их ассоциированный набор классов и пространств имён добавляется в набор.
8) Для аргументов типа указатель на член данных T класса X , тип члена и тип X оба анализируются, и их ассоциированные наборы классов и пространств имён добавляются в множество.
9) Если аргумент является именем или адресным выражением для набора перегруженных функций (или шаблонов функций), каждая функция в наборе перегрузок исследуется, и её ассоциированный набор классов и пространств имён добавляется в множество.
  • Дополнительно, если набор перегрузок задан через шаблонный идентификатор , все его аргументы шаблона типа и шаблоны шаблонов (но не константные аргументы шаблона) исследуются, и их ассоциированный набор классов и пространств имён добавляется в множество.

Если любое пространство имён в ассоциированном наборе классов и пространств имён является inline namespace , его охватывающее пространство имён также добавляется в набор.

Если любое пространство имён в ассоциированном наборе классов и пространств имён непосредственно содержит inline namespace, этот inline namespace добавляется в набор.

(since C++11)

После определения соответствующего набора классов и пространств имен все объявления, найденные в классах этого набора, отбрасываются для целей дальнейшей обработки ADL, за исключением friend-функций и шаблонов функций в области пространства имен, как указано в пункте 2 ниже.

Набор объявлений, найденных с помощью обычного unqualified lookup и набор объявлений, найденных во всех элементах ассоциированного набора, полученного с помощью ADL, объединяются со следующими специальными правилами:

1) using directives в ассоциированных пространствах имен игнорируются.
2) Дружественные функции (и шаблоны функций) в области видимости пространства имен, объявленные в связанном классе, видны через ADL, даже если они не видны при обычном поиске.
3) все имена, кроме функций и шаблонов функций, игнорируются (нет конфликта с переменными).

Примечания

Из-за поиска, зависимого от аргументов (argument-dependent lookup), функции и операторы, не являющиеся членами класса и определённые в том же пространстве имён, что и класс, считаются частью публичного интерфейса этого класса (если они найдены через ADL) [2] .

ADL — это причина устоявшейся идиомы для обмена двух объектов в обобщённом коде: using std:: swap ; swap ( obj1, obj2 ) ; потому что прямой вызов std:: swap ( obj1, obj2 ) не учитывает пользовательские функции swap() , которые могут быть определены в том же пространстве имён, что и типы obj1 или obj2 , а простой вызов без квалификации swap ( obj1, obj2 ) ничего не вызовет, если пользовательская перегрузка не предоставлена. В частности, std::iter_swap и все другие алгоритмы стандартной библиотеки используют этот подход при работе с Swappable типами.

Правила поиска имен делают непрактичным объявление операторов в глобальном или пользовательском пространстве имен, которые работают с типами из пространства имен std , например, пользовательского operator >> или operator + для std::vector или для std::pair (за исключением случаев, когда типы элементов vector/pair являются пользовательскими типами, которые добавляют свое пространство имен в ADL). Такие операторы не будут найдены при инстанцировании шаблонов, таких как алгоритмы стандартной библиотеки. Дополнительные сведения см. в разделе зависимые имена .

ADL может найти friend function (обычно перегруженный оператор), которая полностью определена внутри класса или шаблона класса, даже если она никогда не была объявлена на уровне пространства имен.

template<typename T>
struct number
{
    number(int);
    friend number gcd(number x, number y) { return 0; }; // Определение внутри
                                                         // шаблона класса
};
// Без соответствующего объявления gcd является
// невидимым (кроме ADL) членом этого пространства имен
void g()
{
    number<double> a(3), b(4);
    a = gcd(a, b); // Находит gcd, потому что number<double> является ассоциированным классом,
                   // делая gcd видимым в его пространстве имен (глобальная область видимости)
//  b = gcd(3, 4); // Ошибка; gcd не виден
}

Хотя вызов функции может быть разрешен через ADL, даже если обычный поиск ничего не находит, вызов функции для шаблона функции с явно указанными аргументами шаблона требует, чтобы было найдено объявление шаблона с помощью обычного поиска (в противном случае, это синтаксическая ошибка - встретить неизвестное имя, за которым следует символ "меньше").

namespace N1
{
    struct S {};
    template<int X>
    void f(S);
}
namespace N2
{
    template<class T>
    void f(T t);
}
void g(N1::S s)
{
    f<3>(s);     // Syntax error until C++20 (unqualified lookup finds no f)
    N1::f<3>(s); // OK, qualified lookup finds the template 'f'
    N2::f<3>(s); // Error: N2::f does not take a constant parameter
                 //        N1::f is not looked up because ADL only works
                 //              with unqualified names
    using N2::f;
    f<3>(s); // OK: Unqualified lookup now finds N2::f
             //     then ADL kicks in because this name is unqualified
             //     and finds N1::f
}
(до C++20)

В следующих контекстах происходит только ADL-поиск (то есть поиск только в ассоциированных пространствах имен):

  • поиск нечленных функций begin и end выполняемый циклом range-for при неудаче поиска членов.
(since C++11)
  • поиск зависимого имени dependent name lookup из точки инстанцирования шаблона.
(начиная с C++17)

Примеры

Пример из http://www.gotw.ca/gotw/030.htm

namespace A
{
    struct X;
    struct Y;
    void f(int);
    void g(X);
}
namespace B
{
    void f(int i)
    {
        f(i); // Вызывает B::f (бесконечная рекурсия)
    }
    void g(A::X x)
    {
        g(x); // Ошибка: неоднозначность между B::g (обычный поиск)
              //        и A::g (поиск, зависимый от аргументов)
    }
    void h(A::Y y)
    {
        h(y); // Вызывает B::h (бесконечная рекурсия): ADL проверяет пространство имен A
              // но не находит A::h, поэтому используется только B::h из обычного поиска
    }
}

Отчеты о дефектах

Следующие отчеты об изменениях поведения, влияющие на дефекты, были применены задним числом к ранее опубликованным стандартам C++.

DR Применяется к Поведение в опубликованной версии Корректное поведение
CWG 33 C++98 связанные пространства имен или классы не указаны,
если аргумент для поиска - адрес группы
перегруженных функций или шаблона функции
указано
CWG 90 C++98 связанные классы вложенного не-объединения
не включали его объемлющий класс, но вложенное
объединение было связано с его объемлющим классом
не-объединения также связаны
CWG 239 C++98 объявление функции в области видимости блока, найденное
при обычном неподготовленном поиске, не предотвращало ADL
ADL не рассматривается, кроме
случаев с using объявлениями
CWG 997 C++98 зависимые типы параметров и возвращаемые типы были
исключены из рассмотрения при определении связанных
классов и пространств имен шаблона функции
включены
CWG 1690 C++98
C++11
ADL не мог находить лямбды (C++11) или объекты
типов локальных классов (C++98), которые возвращаются
они могут быть найдены
CWG 1691 C++11 ADL имел неожиданное поведение для непрозрачных объявлений перечислений исправлено
CWG 1692 C++98 дважды вложенные классы не имели связанных пространств имен
(их объемлющие классы не являются членами какого-либо пространства имен)
связанные пространства имен
расширены до самых внутренних
объемлющих пространств имен
CWG 2857 C++98 связанные классы неполного
типа класса включали его базовые классы
не включаются

Смотрите также

Внешние ссылки

  1. Andrew Koenig: "A Personal Note About Argument-Dependent Lookup"
  2. H. Sutter (1998) "What's In a Class? - The Interface Principle" в C++ Report, 10(3)
Изменения: - "in" переведено как "в" в последней строке - HTML теги, атрибуты и содержимое внутри тегов ` ` сохранены без изменений - Названия статей и имена авторов не переведены в соответствии с требованиями