Argument-dependent lookup
인수 종속 조회(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은 고려되지 않습니다:
그렇지 않으면, 함수 호출 표현식의 모든 인수에 대해 해당 인수의 타입을 검사하여 관련된 네임스페이스 및 클래스 집합 을 결정합니다. 이 집합은 조회에 추가될 것입니다.
T
타입에 대한 포인터 인수 또는
T
배열에 대한 포인터 인수의 경우,
T
타입을 검사하고 관련된 클래스 및 네임스페이스 집합을 해당 집합에 추가합니다.
F
의 인수에 대해, 함수 매개변수 타입, 함수 반환 타입, 그리고 클래스
X
가 검사되고 이들의 연관된 클래스 및 네임스페이스 집합이 해당 집합에 추가됩니다.
T
클래스의
X
, 멤버 타입과 타입
X
모두 검사되며 관련된 클래스 및 네임스페이스 집합이 집합에 추가됩니다.
- 추가적으로, 오버로드 집합이 템플릿 식별자 로 명명된 경우, 모든 타입 템플릿 인자와 템플릿 템플릿 인자(상수 템플릿 인자는 제외)를 검사하고 관련된 클래스 및 네임스페이스 집합을 해당 집합에 추가합니다.
|
연관된 클래스 및 네임스페이스 집합에 있는 어떤 네임스페이스가 인라인 네임스페이스 인 경우, 해당 네임스페이스를 감싸는 네임스페이스도 집합에 추가됩니다. 연관된 클래스 및 네임스페이스 집합에 있는 어떤 네임스페이스가 인라인 네임스페이스를 직접 포함하는 경우, 해당 인라인 네임스페이스가 집합에 추가됩니다. |
(C++11부터) |
관련 클래스 및 네임스페이스 집합이 결정된 후, 이 집합의 클래스에서 발견된 모든 선언은 아래 2번 항목에 명시된 대로 네임스펙 범위 friend 함수 및 함수 템플릿을 제외하고, 추가 ADL 처리 목적상 폐기됩니다.
일반적인 비한정 이름 탐색 으로 찾은 선언 집합과 ADL에 의해 생성된 연관 집합의 모든 요소들에서 찾은 선언 집합은 다음 특별 규칙들과 함께 병합됩니다:
참고 사항
인수 종속적 탐색(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 전용 조회(즉, 연관된 네임스페이스에서만 조회)가 수행됩니다:
|
(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); // 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 |
불완전한 클래스 타입의 관련 클래스에
해당 클래스의 기본 클래스들이 포함됨 |
포함되지 않음 |
참고 항목
외부 링크
|