Dependent names
template 정의 내부에서는( class template 과 function template 모두) 일부 구성 요소의 의미가 인스턴스화마다 다를 수 있습니다. 특히, 타입과 표현식은 타입 템플릿 매개변수의 타입과 상수 템플릿 매개변수의 값에 의존할 수 있습니다.
template<typename T> struct X : B<T> // "B<T>"는 T에 종속적임 { typename T::A* pa; // "T::A"는 T에 종속적임 // ("typename" 사용의 의미는 아래 참조) void f(B<T>* pb) { static int i = B<T>::i; // "B<T>::i"는 T에 종속적임 pb->j++; // "pb->j"는 T에 종속적임 } };
이름 lookup 과 바인딩은 종속 이름과 비종속 이름에 대해 다르게 적용됩니다.
목차 |
바인딩 규칙
비종속 이름(non-dependent names)은 템플릿 정의 시점에서 조회되고 바인딩됩니다. 이 바인딩은 템플릿 인스턴스화 시점에 더 나은 매치가 존재하더라도 유지됩니다:
템플릿 특수화의 정의 컨텍스트와 인스턴스화 지점 사이에서 비의존적 이름(non-dependent name)의 의미가 변경되는 경우, 프로그램은 형식 오류이며 진단은 필요하지 않습니다. 이는 다음과 같은 상황에서 발생할 수 있습니다:
- 비의존적 이름에 사용된 타입이 불완전한 타입 으로 정의 지점에 존재하지만, 인스턴스화 지점에서는 완전한 타입이 되는 경우
|
(C++17부터) |
- 인스턴스화가 정의 지점에서 아직 정의되지 않은 기본 인수나 기본 템플릿 인수를 사용하는 경우
- 인스턴스화 지점에서 constant expression 이 정수형이나 비범위 열거형 const 객체의 값 , constexpr 객체의 값, 참조의 값, 또는 constexpr 함수의 정의 (since C++11) 을 사용하고, 해당 객체 /참조/함수 (since C++11) 가 정의 지점에서 정의되지 않은 경우
- 템플릿이 인스턴스화 지점에서 비의존적 클래스 템플릿 특수화 또는 변수 템플릿 특수화 (since C++14) 를 사용하고, 이 사용된 템플릿이 정의 지점에서 정의되지 않은 부분 특수화로부터 인스턴스화되었거나 정의 지점에서 선언되지 않은 명시적 특수화를 지칭하는 경우
종속 이름의 바인딩은 조회가 수행될 때까지 연기됩니다.
조회 규칙
템플릿에서 사용되는 종속 이름(dependent name)의 조회(lookup) 는 템플릿 인자가 알려질 때까지 지연되며, 이 시점에서
- non-ADL lookup는 템플릿 정의 컨텍스트에서 볼 수 있는 외부 링크를 가진 함수 선언을 검사합니다
- ADL 는 템플릿 정의 컨텍스트 또는 템플릿 인스턴스화 컨텍스트에서 볼 수 있는 외부 링크를 가진 함수 선언을 검사합니다
(다시 말해, 템플릿 정의 이후에 새로운 함수 선언을 추가하더라도 ADL을 통한 경우를 제외하고는 해당 함수를 볼 수 없습니다).
이 규칙의 목적은 템플릿 인스턴스화에 대한 ODR 위반을 방지하는 데 도움을 주기 위함입니다:
// 외부 라이브러리 namespace E { template<typename T> void writeObject(const T& t) { std::cout << "Value = " << t << '\n'; } } // 번역 단위 1: // 프로그래머 1은 E::writeObject가 vector<int>와 동작하도록 허용하려 함 namespace P1 { std::ostream& operator<<(std::ostream& os, const std::vector<int>& v) { for (int n : v) os << n << ' '; return os; } void doSomething() { std::vector<int> v; E::writeObject(v); // 오류: P1::operator<<를 찾지 못함 } } // 번역 단위 2: // 프로그래머 2는 E::writeObject가 vector<int>와 동작하도록 허용하려 함 namespace P2 { std::ostream& operator<<(std::ostream& os, const std::vector<int>& v) { for (int n : v) os << n << ':'; return os << "[]"; } void doSomethingElse() { std::vector<int> v; E::writeObject(v); // 오류: P2::operator<<를 찾지 못함 } }
위 예제에서, 인스턴스화 컨텍스트로부터
operator<<
에 대한 비-ADL 탐색이 허용된다면,
E
::
writeObject
<
vector
<
int
>>
의 인스턴스화는 두 가지 서로 다른 정의를 가지게 될 것입니다: 하나는
P1
::
operator
<<
를 사용하고 다른 하나는
P2
::
operator
<<
를 사용합니다. 이러한 ODR 위반은 링커에 의해 감지되지 않을 수 있으며, 두 인스턴스 모두에서 어느 한쪽이 사용되는 결과를 초래할 수 있습니다.
ADL이 사용자 정의 네임스페이스를 검사하도록 하려면, std::vector 를 사용자 정의 클래스로 대체하거나 해당 요소 타입이 사용자 정의 클래스여야 합니다:
namespace P1 { // C가 P1 네임스페이스에 정의된 클래스인 경우 std::ostream& operator<<(std::ostream& os, const std::vector<C>& v) { for (C n : v) os << n; return os; } void doSomething() { std::vector<C> v; E::writeObject(v); // OK: writeObject(std::vector<P1::C>)를 인스턴스화함 // ADL을 통해 P1::operator<<를 찾음 } }
참고: 이 규칙은 표준 라이브러리 타입에 대한 연산자 오버로딩을 비실용적으로 만듭니다:
#include <iostream> #include <iterator> #include <utility> #include <vector> // 나쁜 아이디어: 전역 네임스페이스에 연산자를 정의했지만 인자는 std::에 있음 std::ostream& operator<<(std::ostream& os, std::pair<int, double> p) { return os << p.first << ',' << p.second; } int main() { typedef std::pair<int, double> elem_t; std::vector<elem_t> v(10); std::cout << v[0] << '\n'; // OK: 일반 조회가 ::operator<<를 찾음 std::copy(v.begin(), v.end(), std::ostream_iterator<elem_t>(std::cout, " ")); // 오류: std::ostream_iterator 정의 지점에서의 일반 조회와 // ADL 모두 std 네임스페이스만 고려하며, // std::operator<<의 여러 오버로드를 찾게 되어 조회가 완료됨 // 이후 오버로드 해결 과정에서 elem_t에 대한 operator<<를 // 조회로 찾은 집합에서 발견하지 못함 }
참고: 종속 이름에 대한 제한된 조회(바인딩은 아님)는 템플릿 정의 시점에도 필요에 따라 수행되며, 이는 비종속 이름과 구별하고 현재 인스턴스화의 멤버인지 또는 알 수 없는 특수화의 멤버인지 판단하기 위함입니다. 이 조회를 통해 얻은 정보는 오류 감지에 사용될 수 있으며, 아래를 참조하십시오.
종속 타입
다음 유형들은 dependent types 입니다:
- 템플릿 매개변수
- 알려지지 않은 특수화의 멤버 (아래 참조)
- 알려지지 않은 특수화의 종속 멤버인 중첩 클래스/열거형 (아래 참조)
- 종속 타입의 cv 한정 버전
- 종속 타입으로 구성된 복합 타입
- 요소 타입이 종속적이거나 경계(있는 경우)가 값-종속적인 배열 타입
|
(since C++11) |
- 예외 명세가 값에 의존적인 함수 타입
- 다음 중 하나인 template-id
-
- 템플릿 이름이 템플릿 매개변수인 경우, 또는
- 템플릿 인자 중 하나가 type-dependent이거나 value-dependent인 경우 , 또는 pack expansion인 경우 (C++11부터) (템플릿-id가 인자 목록 없이 사용되는 경우에도, 즉 injected-class-name 으로 사용되는 경우에도 적용됨)
타입 의존적 표현식에 적용된 decltype 의 결과는 고유한 의존 타입입니다. 두 개의 이러한 결과는 해당 표현식들이 동등한 경우에만 동일한 타입을 참조합니다. |
(C++11부터) |
타입 의존적 상수 표현식에 적용된 pack indexing specifier는 고유한 의존 타입입니다. 두 개의 이러한 pack indexing specifier는 해당 상수 표현식들이 동등한 경우에만 동일한 타입을 참조합니다. 그렇지 않은 경우, 두 개의 이러한 pack indexing specifier는 해당 인덱스들이 동일한 값을 가질 때만 동일한 타입을 참조합니다. |
(since C++26) |
참고: 현재 인스턴스화의 typedef 멤버는 그것이 참조하는 타입이 종속적일 때에만 종속적입니다.
타입 종속 표현식
다음 표현식들은 type-dependent 입니다:
- 어떤 하위 표현식이라도 타입 종속 표현식인 표현식
- this , 클래스가 종속 타입인 경우
- 식별자 표현식 으서 concept-id 가 아니고 (C++20부터)
-
- 이름 조회(name lookup)가 하나 이상의 종속 선언(dependent declaration)을 찾는 식별자를 포함하는 경우
- 종속적인 template-id 를 포함하는 경우
|
(C++11부터) |
|
(C++14부터) |
|
(C++17부터) |
|
(C++26부터) |
- 종속 타입으로의 any cast 표현식
- new 표현식 이 종속 타입의 객체를 생성하는 경우
- 현재 인스턴스의 멤버를 참조하는 멤버 접근 표현식으로 해당 멤버의 타입이 종속적인 경우
- 알려지지 않은 특수화의 멤버를 참조하는 멤버 접근 표현식
| (C++17부터) |
|
(C++26부터) |
다음 표현식들은 해당 표현식들의 타입이 될 수 없기 때문에 절대 타입 의존적이지 않습니다:
| (C++11부터) |
| (C++20부터) |
값 종속 표현식
다음 표현식들은 value-dependent 입니다:
- 상수 표현식이 필요한 문맥에서 사용되며, 그 어떤 하위 표현식도 값에 의존적인 표현식
- 다음 조건 중 하나를 만족하는 식별자 표현식 :
|
(C++20부터) |
-
- 이는 타입에 의존적입니다.
- 이는 상수 템플릿 매개변수의 이름입니다.
- 이는 현재 인스턴스화의 의존적 멤버이며 초기화되지 않은 정적 데이터 멤버의 이름입니다.
- 이는 현재 인스턴스화의 의존적 멤버인 정적 멤버 함수의 이름입니다.
- 이는 값 의존적 표현식으로부터 초기화된 정수 또는 열거형 (C++11까지) 리터럴 (C++11 이후) 타입의 상수입니다.
- 피연산자가 타입 종속 표현식인 다음 표현식들:
| (C++11 이후) |
- 피연산자가 종속 타입 식별자인 다음 표현식들:
- 대상 유형이 종속적이거나 피연산자가 유형 종속 표현식인 다음 표현식들:
- 함수 스타일 캐스트 대상 타입이 종속적이거나 값 종속 표현식이 괄호로 둘러싸인 표현식 또는 중괄호 (C++11부터)
|
(C++11부터) |
| (C++17부터) |
- 인수가 현재 인스턴스의 종속 멤버를 명명하는 한정된 식별자 인 주소 연산자 표현식
- 인수가 핵심 상수 표현식 으로 평가될 때 정적 또는 스레드 저장 기간 (C++11부터) 을 가진 객체이거나 멤버 함수인 템플릿된 엔티티 를 참조하는 모든 표현식인 주소 연산자 표현식
종속 이름
|
이 섹션은 불완전합니다
이유: 누락된 [temp.dep]의 도입부 (식별자 표현식 뒤에 괄호로 둘러싸인 목록... |
|
이 섹션은 불완전합니다
이유: 더 명확하게(또는 적어도 덜 위협적으로) 재구성해야 하며, 동시에 CWG issue 591 을 적용해야 함 |
현재 인스턴스화
클래스 템플릿 정의 내부(멤버 함수와 중첩 클래스 포함)에서 일부 이름은 현재 인스턴스화 를 참조하는 것으로 추론될 수 있습니다. 이를 통해 특정 오류들을 인스턴스화 시점이 아닌 정의 시점에 감지할 수 있으며, 아래에서 설명하는 종속 이름에 대한 typename 과 template 명시자 요구사항을 제거합니다.
현재 인스턴스화를 참조할 수 있는 이름은 다음과 같습니다:
-
클래스 템플릿, 클래스 템플릿의 중첩 클래스, 클래스 템플릿의 멤버, 또는 클래스 템플릿의 중첩 클래스 멤버 정의 내에서:
- 클래스 템플릿 또는 중첩 클래스의 주입된 클래스 이름(injected-class-name)
-
기본 클래스 템플릿(primary class template) 또는 기본 클래스 템플릿 멤버 정의 내에서:
- 기본 템플릿에 대한 템플릿 인자 목록이 뒤따르는 클래스 템플릿 이름 (또는 이에 상응하는 별칭 템플릿 특수화). 각 인자는 해당 매개변수와 동등(equivalent)해야 함
-
클래스 템플릿의 중첩 클래스 정의 내에서:
- 현재 인스턴스(current instantiation)의 멤버로 사용되는 중첩 클래스 이름
-
클래스 템플릿 부분 특수화(class template partial specialization) 또는 클래스 템플릿 부분 특수화 멤버 정의 내에서:
- 부분 특수화에 대한 템플릿 인자 목록이 뒤따르는 클래스 템플릿 이름. 각 인자는 해당 매개변수와 동등해야 함
-
템플릿화된 함수(templated function)
정의 내에서:
- 지역 클래스(local class) 의 이름
템플릿 인자는 다음과 같은 경우 템플릿 매개변수와 동등합니다
- 타입 매개변수의 경우, 템플릿 인자는 템플릿 매개변수와 동일한 타입을 나타냅니다.
- 상수 매개변수의 경우, 템플릿 인자는 템플릿 매개변수와 동등한 변수를 지칭하는 식별자입니다. 변수는 다음 조건에서 템플릿 매개변수와 동등합니다:
-
- 템플릿 매개변수와 동일한 타입을 가지며(cv 한정자는 무시)
- 해당 초기화자가 템플릿 매개변수의 이름인 단일 식별자로 구성되거나, 재귀적으로 그러한 변수로 구성되는 경우
template<class T> class A { A* p1; // A는 현재 인스턴스화입니다 A<T>* p2; // A<T>는 현재 인스턴스화입니다 ::A<T>* p4; // ::A<T>는 현재 인스턴스화입니다 A<T*> p3; // A<T*>는 현재 인스턴스화가 아닙니다 class B { B* p1; // B는 현재 인스턴스화입니다 A<T>::B* p2; // A<T>::B는 현재 인스턴스화입니다 typename A<T*>::B* p3; // A<T*>::B는 현재 인스턴스화가 아닙니다 }; }; template<class T> class A<T*> { A<T*>* p1; // A<T*>는 현재 인스턴스화입니다 A<T>* p2; // A<T>는 현재 인스턴스화가 아닙니다 }; template<int I> struct B { static const int my_I = I; static const int my_I2 = I + 0; static const int my_I3 = my_I; static const long my_I4 = I; static const int my_I5 = (I); B<my_I>* b1; // B<my_I>는 현재 인스턴스화입니다: // my_I는 I와 동일한 타입을 가지며, // 오직 I로만 초기화됩니다 B<my_I2>* b2; // B<my_I2>는 현재 인스턴스화가 아닙니다: // I + 0은 단일 식별자가 아닙니다 B<my_I3>* b3; // B<my_I3>는 현재 인스턴스화입니다: // my_I3는 I와 동일한 타입을 가지며, // 오직 my_I(I와 동등함)로만 초기화됩니다 B<my_I4>* b4; // B<my_I4>는 현재 인스턴스화가 아닙니다: // my_I4의 타입(long)이 I의 타입(int)과 동일하지 않습니다 B<my_I5>* b5; // B<my_I5>는 현재 인스턴스화가 아닙니다: // (I)는 단일 식별자가 아닙니다 };
중첩 클래스가 자신을 둘러싼 클래스 템플릿에서 파생된 경우 기본 클래스가 현재 인스턴스화(current instantiation)가 될 수 있음에 유의하십시오. 종속 타입이지만 현재 인스턴스화가 아닌 기본 클래스는 종속 기본 클래스(dependent base classes) 입니다:
template<class T> struct A { typedef int M; struct B { typedef void M; struct C; }; }; template<class T> struct A<T>::B::C : A<T> { M m; // 정상, A<T>::M };
이름은 다음과 같은 경우 현재 인스턴스화의 멤버로 분류됩니다
- 현재 인스턴스화 또는 그 비의존적 기저에서 비한정 이름 검색 으로 발견된 비한정 이름
-
한정된 이름
(단, 한정자(
::의 왼쪽에 위치한 이름)가 현재 인스턴스화를 지칭하고, 검색이 현재 인스턴스화 또는 그 비의존적 기저에서 이름을 발견하는 경우) - 클래스 멤버 접근 표현식에서 사용된 이름 ( x. y 또는 xp - > y 에서의 y ), 단 객체 표현식( x 또는 * xp )이 현재 인스턴스화이고, 검색이 현재 인스턴스화 또는 그 비의존적 기저에서 이름을 발견하는 경우
template<class T> class A { static const int i = 5; int n1[i]; // i는 현재 인스턴스화의 멤버를 참조함 int n2[A::i]; // A::i는 현재 인스턴스화의 멤버를 참조함 int n3[A<T>::i]; // A<T>::i는 현재 인스턴스화의 멤버를 참조함 int f(); }; template<class T> int A<T>::f() { return i; // i는 현재 인스턴스화의 멤버를 참조함 }
현재 인스턴스화의 멤버는 종속적일 수도 있고 비종속적일 수도 있습니다.
현재 인스턴스화의 멤버 조회가 인스턴스화 지점과 정의 지점 사이에서 다른 결과를 제공하면, 해당 조회는 모호합니다. 그러나 멤버 이름이 사용될 때 자동으로 클래스 멤버 접근 표현식으로 변환되지 않으며, 명시적 멤버 접근 표현식만이 현재 인스턴스화의 멤버를 나타낸다는 점에 유의하십시오:
struct A { int m; }; struct B { int m; }; template<typename T> struct C : A, T { int f() { return this->m; } // 템플릿 정의 컨텍스트에서 A::m을 찾음 int g() { return m; } // 템플릿 정의 컨텍스트에서 A::m을 찾음 }; template int C<B>::f(); // 오류: A::m과 B::m을 모두 찾음 template int C<B>::g(); // OK: 클래스 멤버 접근 구문으로의 변환은 // 템플릿 정의 컨텍스트에서 발생하지 않음
알려지지 않은 특수화
템플릿 정의 내에서 특정 이름들은 unknown specialization 에 속하는 것으로 추론됩니다, 특히,
-
한정된 이름
(
::의 왼쪽에 나타나는 이름 중 현재 인스턴스의 멤버가 아닌 종속 타입이 있는 경우) - 한정자가 현재 인스턴스이고, 현재 인스턴스 또는 비종속 기본 클래스에서 이름을 찾을 수 없으며 종속 기본 클래스가 존재하는 한정된 이름
- 클래스 멤버 접근 표현식의 멤버 이름 ( x. y 또는 xp - > y 에서 y ), 객체 표현식의 타입 ( x 또는 * xp )이 종속 타입이고 현재 인스턴스가 아닌 경우
- 클래스 멤버 접근 표현식의 멤버 이름 ( x. y 또는 xp - > y 에서 y ), 객체 표현식의 타입 ( x 또는 * xp )이 현재 인스턴스이고, 현재 인스턴스 또는 비종속 기본 클래스에서 이름을 찾을 수 없으며 종속 기본 클래스가 존재하는 경우
template<typename T> struct Base {}; template<typename T> struct Derived : Base<T> { void f() { // Derived<T>는 현재 인스턴스화를 참조함 // 현재 인스턴스화에는 "unknown_type"이 존재하지 않음 // 그러나 종속 베이스(Base<T>)가 존재함 // 따라서 "unknown_type"은 알려지지 않은 특수화의 멤버임 typename Derived<T>::unknown_type z; } }; template<> struct Base<int> // 이 특수화에서 제공함 { typedef int unknown_type; };
이 분류를 통해 템플릿 정의 시점(인스턴스화가 아닌)에 다음과 같은 오류들을 검출할 수 있습니다:
- 템플릿 정의에서 한정된 이름(qualified name) 이 현재 인스턴스화(current instantiation)를 참조하는 한정자를 가지며, 해당 이름이 현재 인스턴스화의 멤버도 아니고 알 수 없는 특수화(unknown specialization)의 멤버도 아닌 경우, 템플릿이 인스턴스화되지 않더라도 프로그램은 형식 오류(ill-formed)입니다 (진단 메시지 없음).
template<class T> class A { typedef int type; void f() { A<T>::type i; // OK: "type"는 현재 인스턴스화의 멤버입니다 typename A<T>::other j; // 오류: // "other"는 현재 인스턴스화의 멤버가 아니며 // 알려지지 않은 특수화의 멤버도 아닙니다 // 왜냐하면 A<T> (현재 인스턴스화를 지칭하는)는 // "other"가 숨을 수 있는 의존적 기저 클래스가 없기 때문입니다 } };
- 템플릿 정의에서 객체 표현식이 현재 인스턴스화이지만, 해당 이름이 현재 인스턴스화의 멤버도 아니고 알 수 없는 특수화의 멤버도 아닌 멤버 접근 표현식이 있을 경우, 템플릿이 인스턴스화되지 않더라도 프로그램은 ill-formed입니다.
알려지지 않은 특수화의 멤버는 항상 종속적이며, 모든 종속 이름과 마찬가지로 인스턴스화 지점에서 조회되고 바인딩됩니다 (위 참조)
종속 이름에 대한 typename 명시자
템플릿의 선언 또는 정의(별칭 템플릿 포함)에서, 현재 인스턴스화의 멤버가 아니고 템플릿 매개변수에 종속된 이름은, typename 키워드를 사용하거나 typedef 선언으로 타입 이름으로 이미 확립되었거나 기본 클래스의 이름을 지정하는 데 사용되는 경우와 같은 경우를 제외하고는 타입으로 간주되지 않습니다.
#include <iostream> #include <vector> int p = 1; template<typename T> void foo(const std::vector<T> &v) { // std::vector<T>::const_iterator는 종속 이름입니다, typename std::vector<T>::const_iterator it = v.begin(); // "typename" 없이는 다음은 타입 종속 데이터 멤버 "const_iterator"와 // 어떤 변수 "p"의 곱셈으로 파싱됩니다. 이 시점에서 전역 "p"가 보이므로 // 이 템플릿 정의는 컴파일됩니다. std::vector<T>::const_iterator* p; typedef typename std::vector<T>::const_iterator iter_t; iter_t * p2; // "iter_t"는 종속 이름이지만 타입 이름으로 알려져 있습니다 } template<typename T> struct S { typedef int value_t; // 현재 인스턴스의 멤버 void f() { S<T>::value_t n{}; // S<T>는 종속적이지만 "typename"이 필요하지 않음 std::cout << n << '\n'; } }; int main() { std::vector<int> v; foo(v); // 템플릿 인스턴스화 실패: std::vector<int> 타입에 // "const_iterator"라는 멤버 변수가 존재하지 않음 S<int>().f(); }
키워드 typename 는 한정된 이름 앞에서만 이런 방식으로 사용될 수 있습니다 (예: T :: x ), 하지만 이름이 반드시 종속적일 필요는 없습니다.
일반적인 한정 이름 조회 가 typename 으로 접두사된 식별자에 사용됩니다. 상세 타입 지정자 의 경우와 달리, 한정자가 있더라도 조회 규칙은 변경되지 않습니다:
struct A // A는 중첩 변수 X와 중첩 타입 struct X를 가짐 { struct X {}; int X; }; struct B { struct X {}; // B는 중첩 타입 struct X를 가짐 }; template<class T> void f(T t) { typename T::X x; } void foo() { A a; B b; f(b); // OK: f<B>를 인스턴스화함, T::X는 B::X를 참조 f(a); // 오류: f<A>를 인스턴스화할 수 없음: // A::X에 대한 한정 이름 조회가 데이터 멤버를 찾기 때문 }
키워드 typename 는 템플릿 외부에서도 사용할 수 있습니다.
#include <vector> int main() { // 둘 다 OK (CWG 382 해결 이후) typedef typename std::vector<int>::const_iterator iter_t; typename std::vector<int> v; }
|
어떤 문맥에서는 오직 타입 이름만 유효하게 나타날 수 있습니다. 이러한 문맥에서는 종속적 한정 이름(dependent qualified name)이 타입을 지칭하는 것으로 간주되며 typename 이 필요하지 않습니다:
|
(C++20부터) |
종속 이름에 대한 template 모호성 제거자
마찬가지로, 템플릿 정의에서 현재 인스턴스화의 멤버가 아닌 종속 이름은 분명히 지정하는 키워드 template 를 사용하거나 이미 템플릿 이름으로 확립되지 않는 한 템플릿 이름으로 간주되지 않습니다:
template<typename T> struct S { template<typename U> void foo() {} }; template<typename T> void bar() { S<T> s; s.foo<T>(); // 오류: <가 less than 연산자로 파싱됨 s.template foo<T>(); // 정상 }
키워드 template 는 오직 연산자 :: (범위 지정), - > (포인터를 통한 멤버 접근), 그리고 . (멤버 접근) 다음에만 이런 방식으로 사용될 수 있습니다. 다음은 모두 유효한 예시들입니다:
- T :: template foo < X > ( ) ;
- s. template foo < X > ( ) ;
- this - > template foo < X > ( ) ;
- typename T :: template iterator < int > :: value_type v ;
`, `
`, `
typename 의 경우와 마찬가지로, template 접두사는 이름이 종속적이지 않거나 사용이 템플릿 범위 내에 나타나지 않는 경우에도 허용됩니다.
왼쪽의 이름이
::
네임스페이스를 참조하더라도, 템플릿 명시자는 허용됩니다:
template<typename> struct S {}; ::template S<void> q; // 허용되지만 불필요함
|
템플릿 이름에 대한 비한정 이름 검색 의 특별 규칙으로 인해, 비의존적 템플릿 이름이 멤버 접근 표현식( - > 이후 또는 . 이후)에 나타날 때, 표현식의 문맥에서 일반 검색으로 동일한 이름을 가진 클래스 또는 별칭 (C++11부터) 템플릿이 발견되면 명시자(template)는 불필요합니다. 그러나 표현식 문맥에서 검색된 템플릿이 클래스 문맥에서 검색된 템플릿과 다를 경우 프로그램은 잘못된 형식입니다 (C++11까지) template<int> struct A { int value; }; template<class T> void f(T t) { t.A<0>::value; // A의 일반 검색이 클래스 템플릿을 찾음. // A<0>::value는 클래스 A<0>의 멤버를 지칭 // t.A < 0; // 오류: "<"가 템플릿 인수 목록의 시작으로 처리됨 } |
(C++23까지) |
키워드
결함 보고서
다음 동작 변경 결함 보고서는 이전에 게시된 C++ 표준에 소급 적용되었습니다.
| DR | 적용 대상 | 게시된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 206 | C++98 |
템플릿이 정의된 지점에서 불완전하지만 인스턴스화가 수행되는 지점에서
완전한 타입이 비의존적 이름에 사용될 때 시맨틱 제약 조건이 적용되는 시점이 명시되지 않았음 |
이 경우 프로그램은 형식 오류이며
진단 메시지가 필요하지 않음 |
| CWG 224 | C++98 |
의존적 타입의 정의가 이름 조회보다는
이름의 형태에 기반했음 |
정의 개정됨 |
| CWG 382 | C++98 | typename 명시자는 템플릿 범위에서만 허용됨 |
템플릿 외부에서도
허용됨 |
| CWG 468 | C++98 | template 명시자는 템플릿 범위에서만 허용됨 |
템플릿 외부에서도
허용됨 |
| CWG 502 | C++98 | 중첩 열거형이 의존적인지 여부가 명시되지 않았음 | 중첩 클래스와 동일하게 의존적임 |
| CWG 1047 | C++98 | typeid 표현식은 값-의존적이지 않았음 |
피연산자가 타입-의존적이면
값-의존적임 |
| CWG 1160 | C++98 |
템플릿의 멤버 정의 내에서 기본 템플릿 또는 부분 특수화와 일치하는
템플릿 ID가 나타날 때 이름이 현재 인스턴스를 참조하는지 여부가 명시되지 않았음 |
명시됨 |
| CWG 1413 | C++98 |
초기화되지 않은 정적 데이터 멤버, 정적 멤버 함수 및 클래스 템플릿의
멤버 주소가 값-의존적 목록에 포함되지 않았음 |
포함됨 |
| CWG 1471 | C++98 |
현재 인스턴스의 비의존적 기본 클래스의
중첩 타입이 의존적이었음 |
의존적이지 않음 |
| CWG 1850 | C++98 |
정의 컨텍스트와 인스턴스화 지점 사이에서 의미가 변경될 수 있는
경우의 목록이 불완전했음 |
완전하게 만듦 |
| CWG 1929 | C++98 |
template
명시자가 왼쪽 이름이 네임스페이스를 참조하는
::
뒤에 올 수 있는지 명확하지 않았음
|
허용됨 |
| CWG 2066 | C++98 | this 는 값-의존적이지 않았음 | 값-의존적일 수 있음 |
| CWG 2100 | C++98 |
클래스 템플릿의 정적 데이터 멤버 주소가
값-의존적 목록에 포함되지 않았음 |
포함됨 |
| CWG 2109 | C++98 | 타입-의존적 식별자 표현식이 값-의존적이지 않을 수 있었음 | 항상 값-의존적임 |
| CWG 2276 | C++98 |
예외 명세가 값-의존적인 함수 타입이
의존적 타입이 아니었음 |
의존적 타입임 |
| CWG 2307 | C++98 |
괄호로 묶인 상수 템플릿 매개변수가 템플릿 인수로 사용될 때
해당 템플릿 매개변수와 동등했음 |
더 이상 동등하지 않음 |
| CWG 2457 | C++11 |
함수 매개변수 팩을 가진 함수 타입이
의존적 타입이 아니었음 |
의존적 타입임 |
| CWG 2785 | C++20 | requires 표현식이 타입-의존적일 수 있었음 | 절대 타입-의존적이지 않음 |
| CWG 2905 | C++11 |
noexcept
표현식은 피연산자가 값-의존적일 때만
값-의존적이었음 |
피연산자가 템플릿 매개변수를
포함하면 값-의존적임 |
| CWG 2936 | C++98 |
템플릿화된 함수의 지역 클래스 이름이
현재 인스턴스의 일부가 아니었음 |
현재 인스턴스의 일부임 |