Spec-Zone.ru › C++

Поиск с учётом аргументов

Поиск с учётом аргументов, также известный как ADL (Argument-dependent lookup) или поиск Коэнига [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) Для аргументов фундаментального типа связанный набор пространств имён и классов пуст.
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)

В следующих контекстах выполняется поиск только с учётом аргументов (т. е. поиск только в связанных пространствах имён):

  • поиск нечленных функций 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 не учитывается, кроме
использования объявлений
CWG 997 C++98 зависимые типы параметров и возвращаемые типы были исключены из рассмотрения
при определении связанных классов и пространств имён шаблона функции
включены
CWG 1690 C++98
C++11
ADL не мог найти лямбда-выражения (C++11) или объекты
локальных типов классов (C++98), которые возвращаются
они могут быть найдены
CWG 1691 C++11 ADL имел неожиданное поведение для неявных объявлений перечислений исправлено
CWG 1692 C++98 классы с двойным вложением не имели связанных пространств имён
(их внешние классы не являются членами пространства имён)
связанные пространства имён
расширены до самых внутренних
внешних пространств имён

См. также

  • Поиск имён
  • Вывод аргументов шаблона
  • Разрешение перегрузки

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

  1. Andrew Koenig: "Заметка о поиске аргументов, зависящих от аргументов"
  2. H. Sutter (1998) "Что внутри класса? - Принцип интерфейса" в C++ Report, 10(3)

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

Spec-Zone.ru

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