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, Argument-dependent lookup), 또는 쾨니히 조회(Koenig lookup) [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: 이는 함수 호출임. endl의 인수가 std에 있으므로
                     // ADL이 std 네임스페이스를 검사하고 std::endl을 찾음
    (endl)(std::cout); // 오류: "endl"이 이 네임스페이스에서 선언되지 않음
                       // 부분 표현식 (endl)은 비한정 식별자(unqualified-id)가 아님
}

목차

상세 정보

먼저, 일반적인 unqualified lookup 으로 생성된 lookup set이 다음 중 하나를 포함하는 경우, 인수 종속 lookup은 고려되지 않습니다:

1) 클래스 멤버의 선언.
2) 블록 범위에서의 함수 선언 (이것은 using declaration 가 아닙니다).
3) 함수 또는 함수 템플릿이 아닌 선언(예: 함수 객체 또는 조회 중인 함수의 이름과 충돌하는 다른 변수).

그렇지 않으면, 함수 호출 표현식의 모든 인수에 대해 해당 인수의 타입을 검사하여 관련된 네임스페이스 및 클래스 집합 을 결정합니다. 이 집합은 조회에 추가될 것입니다.

1) 기본 타입 인수의 경우, 관련된 네임스페이스 및 클래스 집합은 비어 있습니다.
2) 클래스 타입(union 포함) 인수에 대해, 집합은 다음으로 구성됩니다:
a) 클래스 자체.
b) 클래스가 완전한(complete) 경우, 해당 클래스의 모든 직접 및 간접 기본 클래스.
c) 클래스가 다른 클래스의 멤버인 경우 , 해당 클래스가 멤버로 속한 클래스.
d) 집합에 추가된 클래스들의 가장 내부에 있는 둘러싸는 네임스페이스들.
3) 인수의 타입이 class template 특수화인 경우, 클래스 규칙에 더하여 다음의 연관된 클래스와 네임스페이스가 집합에 추가됩니다.
a) 타입 템플릿 매개변수에 제공된 모든 템플릿 인수의 유형들 (상수 템플릿 매개변수는 건너뛰고, 템플릿 템플릿 매개변수는 건너뜀).
b) 템플릿 템플릿 인수가 멤버로 속하는 네임스페이스들.
c) 템플릿 템플릿 인자가 구성원인 클래스들(이들이 우연히 클래스 멤버 템플릿인 경우).
4) 열거형 타입의 인수에 대해, 열거형 타입의 선언이 정의된 가장 내부의 둘러싸는 네임스페이스가 집합에 추가됩니다. 열거형 타입이 클래스의 멤버인 경우, 해당 클래스가 집합에 추가됩니다.
5) T 타입에 대한 포인터 인수 또는 T 배열에 대한 포인터 인수의 경우, T 타입을 검사하고 관련된 클래스 및 네임스페이스 집합을 해당 집합에 추가합니다.
6) 함수 타입 인자의 경우, 함수 매개변수 타입과 함수 반환 타입을 검사하고 관련된 클래스 및 네임스페이스 집합이 해당 집합에 추가됩니다.
7) 멤버 함수 포인터 타입 F 의 인수에 대해, 함수 매개변수 타입, 함수 반환 타입, 그리고 클래스 X 가 검사되고 이들의 연관된 클래스 및 네임스페이스 집합이 해당 집합에 추가됩니다.
8) 데이터 멤버에 대한 포인터 타입의 인수에 대해 T 클래스의 X , 멤버 타입과 타입 X 모두 검사되며 관련된 클래스 및 네임스페이스 집합이 집합에 추가됩니다.
9) 인수가 오버로드된 함수 집합(또는 함수 템플릿)의 이름이나 주소 연산자 표현식 인 경우, 오버로드 집합에 포함된 모든 함수를 검사하고 관련된 클래스 및 네임스페이스 집합을 해당 집합에 추가합니다.
  • 추가적으로, 오버로드 집합이 템플릿 식별자 로 명명된 경우, 모든 타입 템플릿 인자와 템플릿 템플릿 인자(상수 템플릿 인자는 제외)를 검사하고 관련된 클래스 및 네임스페이스 집합을 해당 집합에 추가합니다.

연관된 클래스 및 네임스페이스 집합에 있는 어떤 네임스페이스가 인라인 네임스페이스 인 경우, 해당 네임스페이스를 감싸는 네임스페이스도 집합에 추가됩니다.

연관된 클래스 및 네임스페이스 집합에 있는 어떤 네임스페이스가 인라인 네임스페이스를 직접 포함하는 경우, 해당 인라인 네임스페이스가 집합에 추가됩니다.

(C++11부터)

관련 클래스 및 네임스페이스 집합이 결정된 후, 이 집합의 클래스에서 발견된 모든 선언은 아래 2번 항목에 명시된 대로 네임스펙 범위 friend 함수 및 함수 템플릿을 제외하고, 추가 ADL 처리 목적상 폐기됩니다.

일반적인 비한정 이름 탐색 으로 찾은 선언 집합과 ADL에 의해 생성된 연관 집합의 모든 요소들에서 찾은 선언 집합은 다음 특별 규칙들과 함께 병합됩니다:

1) using directives 는 관련 네임스페이스에서 무시됩니다.
2) 연관된 클래스 내에서 선언된 namespace-scoped friend 함수(및 함수 템플릿)는 일반 조회를 통해 보이지 않더라도 ADL을 통해 가시적입니다.
3) 함수와 함수 템플릿을 제외한 모든 이름은 무시됩니다(변수와 충돌하지 않음).

참고 사항

인수 종속적 탐색(argument-dependent lookup) 때문에, 클래스와 동일한 네임스페이스에 정의된 비멤버 함수와 비멤버 연산자는 해당 클래스의 공개 인터페이스의 일부로 간주됩니다(ADL을 통해 발견되는 경우) [2] .

ADL은 일반화된 코드에서 두 객체를 교환하기 위한 확립된 관용구의 배경 이유입니다: using std:: swap ; swap ( obj1, obj2 ) ; 왜냐하면 직접 std:: swap ( obj1, obj2 ) 를 호출할 경우 obj1 이나 obj2 의 타입과 동일한 네임스페이스에 정의될 수 있는 사용자 정의 swap() 함수들을 고려하지 않기 때문이며, 한정되지 않은 swap ( obj1, obj2 ) 를 호출할 경우 사용자 정의 오버로드가 제공되지 않으면 아무것도 호출하지 않기 때문입니다. 특히, std::iter_swap 및 다른 모든 표준 라이브러리 알고리즘들은 Swappable 타입들을 다룰 때 이 접근 방식을 사용합니다.

이름 검색 규칙 때문에 전역 또는 사용자 정의 네임스페이스에서 std 네임스페이스의 타입을 연산하는 연산자들을 선언하는 것은 실용적이지 않습니다. 예를 들어 operator >> operator + std::vector std::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); // number<double>이 연관 클래스이므로 gcd를 찾음,
                   // 이로 인해 gcd가 해당 네임스페이스(전역 범위)에서 보이게 됨
//  b = gcd(3, 4); // 오류; gcd가 보이지 않음
}

일반 lookup이 아무것도 찾지 못하더라도 ADL을 통해 함수 호출이 해결될 수 있지만, 명시적으로 지정된 템플릿 인자를 가진 function template 에 대한 함수 호출은 일반 lookup으로 템플릿 선언을 찾을 수 있어야 합니다 (그렇지 않으면, 알 수 없는 이름 뒤에 보다 작은 기호가 나오는 것은 구문 오류입니다).

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 전용 조회(즉, 연관된 네임스페이스에서만 조회)가 수행됩니다:

  • 멤버 조회가 실패할 경우 range-for 루프에서 수행되는 비멤버 함수 begin end 의 조회
(C++11부터)
(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 lookup에 사용되는 인수가 오버로드된 함수 그룹의 주소나
함수 템플릿인 경우 관련 네임스페이스나 클래스가 지정되지 않음
지정됨
CWG 90 C++98 중첩된 non-union 클래스의 관련 클래스에
자신을 감싸는 클래스가 포함되지 않았지만,
중첩된 union은 자신을 감싸는 클래스와 연관됨
non-union도 연관됨
CWG 239 C++98 일반적인 unqualified lookup에서 찾은 블록 범위 함수 선언이
ADL이 발생하는 것을 막지 않음
using 선언을 제외하고
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 이중으로 중첩된 클래스는 관련 네임스페이스를 가지지 않음
(이들을 감싸는 클래스들은 어떤 네임스페이스의 멤버도 아님)
관련 네임스페이스가
가장 안쪽의 감싸는
네임스페이스까지 확장됨
CWG 2857 C++98 불완전한 클래스 타입의 관련 클래스에
해당 클래스의 기본 클래스들이 포함됨
포함되지 않음

참고 항목

외부 링크

  1. Andrew Koenig: "인수 종속 이름 탐색에 관한 개인적 기록"
  2. H. Sutter (1998) "클래스에는 무엇이 있는가? - 인터페이스 원칙" in C++ Report, 10(3)