Spec-Zone.ru › C++

Поиск, зависящий от аргументов

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

Поиск, зависящий от аргументов, позволяет использовать операторы, определённые в другом пространстве имён. Пример:

#include <iostream>

int main()
{
    std::cout << "Test\n"; // There is no operator<< in global namespace, but ADL
                           // examines std namespace because the left argument is in
                           // std and finds std::operator<<(std::ostream&, const char*)
    operator<<(std::cout, "Test\n"); // Same, using function call notation
    
    // However,
    std::cout << endl; // Error: “endl” is not declared in this namespace.
                       // This is not a function call to endl(), so ADL does not apply
    
    endl(std::cout); // OK: this is a function call: ADL examines std namespace
                     // because the argument of endl is in std, and finds std::endl
    
    (endl)(std::cout); // Error: “endl” is not declared in this namespace.
                       // The sub-expression (endl) is not an unqualified-id
}

Подробности

Сначала поиск, зависящий от аргументов, не выполняется, если множество результатов обычного поиска неквалифицированных имён содержит что-либо из следующего:

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

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

1) Для аргументов фундаментального типа, кроме std::meta::info(начиная с C++26), связанное множество пространств имён и классов пусто.

Для std::meta::info пространство имён, в котором находится перечислимый тип std::meta::operators, добавляется в множество.

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

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

Если какое-либо пространство имён в связанном множестве классов и пространств имён непосредственно содержит встроенное пространство имён, это встроенное пространство имён добавляется в множество.

(начиная с C++11)

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

Множество объявлений, найденных обычным поиском неквалифицированных имён, и множество объявлений, найденных во всех элементах связанного множества, полученного с помощью ADL, объединяются со следующими особыми правилами:

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

Примечания

Благодаря поиску, зависящему от аргументов, функции и операторы, не являющиеся членами класса и определённые в том же пространстве имён, что и класс, считаются частью открытого интерфейса этого класса (если они найдены с помощью 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 (если только типы элементов вектора/пары не являются пользовательскими типами, что добавило бы их пространство имён в ADL). Такие операторы не будут найдены при инстанцировании шаблонов, например алгоритмов стандартной библиотеки. Дополнительные сведения см. в разделе зависимые имена.

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

template<typename T>
struct number
{
    number(int);
    friend number gcd(number x, number y) { return 0; }; // Definition within
                                                         // a class template
};

// Unless a matching declaration is provided gcd is
// an invisible (except through ADL) member of this namespace
void g()
{
    number<double> a(3), b(4);
    a = gcd(a, b); // Finds gcd because number<double> is an associated class,
                   // making gcd visible in its namespace (global scope)
//  b = gcd(3, 4); // Error; gcd is not visible
}

Хотя вызов функции может быть разрешён с помощью 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 non-type 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, если поиск членов класса завершается неудачей.
(начиная с C++11)
  • поиск зависимых имён в точке инстанцирования шаблона.
  • поиск функции, не являющейся членом класса, get, выполняемый объявлением структурированной привязки для типов, подобных кортежам.
(начиная с 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); // Calls B::f (endless recursion)
    }
    
    void g(A::X x)
    {
        g(x); // Error: ambiguous between B::g (ordinary lookup)
              //        and A::g (argument-dependent lookup)
    }
    
    void h(A::Y y)
    {
        h(y); // Calls B::h (endless recursion): ADL examines the A namespace
              // but finds no A::h, so only B::h from ordinary lookup is used
    }
}

Отчёты об ошибках стандарта

Следующие отчёты об ошибках, изменяющие поведение, были применены задним числом к ранее опубликованным стандартам 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. Эндрю Кёниг: «Личное замечание о поиске, зависящем от аргументов»
  2. Х. Саттер (1998) «Что такое класс? — Принцип интерфейса» в C++ Report, 10(3)

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/adl

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API