템플릿의 타입 매개변수를 선언할 떄는 class와 typename의 뜻이 완전히 똑같다. 그렇다고 언제까지나 class와 typename이 C++ 앞에서 언제나 동등한 것만은 아니다.
template< typename C >
void Print2nd( const C& container)
{
if( container.size() >= 2 )
{
C::const_iterator iter( container.begin() );
++iter;
int value = *iter;
std::cout << value;
}
}
이 템플릿은 STL과 호환되는 컨테이너를 받아들이도록 만들어졌고, 이 컨테이너에 담기는 객체는 int에 대입할 수 있다. 이 템플릿이 하는 일은 컨테이너에 담긴 원소들 중 두 번째 것의 값을 출력하는 것뿐이다.
iter의 타입은 C::const_iterator인데. 템플릿 매개변수인 C에 따라 달라지는 타입이다. 템플릿 내의 이름 중에 이렇게 템플릿 매개변수에 종속된 것을 가리켜 의존 이름이라고 한다. 의존 이름이 어떤 클래스 안에 중첩되어 있는 경우가 있는데. 이 경우의 이름을 중첩 의존 (타입) 이름이라고 한다.
value는 int 타입이다. int는 템플릿 매개변수가 어떻든 상관없는 타입 이름이다. 이러한 이름은 비의존 이름이라고 한다.
코드 안에 중첩 의존 이름이 있으면 골치 아픈 일이 생길 수 있다. 컴파일러가 구문분석을 할 때 문제가 발생한다.
template< typename C >
void Print2nd( const C& container )
{
C::const_iterator* x;
...
}
언뜻 보면, C::const_iterator에 대한 포인터인 지역 변수로서 x를 선언하고 있는 것 같지만, 우연히 const_iterator라는 이름을 가진 정적 데이터 멤버가 C에 들어있고, x가 다른 전역 변수의 이름이라면 그냥 C::const_iterator와 x를 피연산자로 한 곱셈 연산이 된다.
C의 정체가 무엇인지 다른 곳에서 알려 주지 않으면 C::const_iterator가 진짜 차입인지 아닌지를 알아낼 방법은 없다. C++은 모호성을 해결하기 위해 어떤 규칙을 하나 사용한다. 이 규칙에 의하면, 구문 분석기는 템플릿 안에서 중첩 의존 이름을 만나면 직접 타입을 알려 주지 않는 한 그 이름이 타입이 아니라고 가정하게 되어 있다. 다시 말해, 중첩 의존 이름은 기본적으로 타입이 아닌 것으로 해석된다.
iter의 선언이 선언으로서 의미가 있으려면 C::const_iterator가 반드시 타입이어야 하는데, C++ 컴파일러에게 타입이라고 알려 주지 않았으니, C++는 제멋대로 타입이 아닌 것으로 가정해 버린다.
template< typename C >
void Print2nd( const C& container )
{
if( container.size() >= 2 )
{
typename C::const_iterator iter( container.begin() );
++iter;
int value = *iter;
std::cout << value;
}
}
C::const_iterator 앞에다가 typename이라는 키워드를 붙혀 놓는다.
typename 키워드는 중첩 의존 이름만 식별하는 데 써야 한다. 그 외의 이름은 typename을 가져선 안된다.
template< typename C >
void f( const C& container, // typename 쓰면 안 됨
typename C::iterator iter ) // typename 꼭 써야 함
{
...
}
C는 중첩 의존 타입 이름이 아니기 때문에, container를 선언할 때는 typename을 이 앞에 붙이면 안 된다. 반면에 C::iterator는 분명히 중첩 의존 이름이기 때문에, 이 앞에는 typename이 반드시 붙어야만 한다.
template< typename T >
class Derived : public Base< T >::Nested // 상속되는 기본 클래스 리스트. typename을 쓰면 안 됨
{
public :
explicit Derived( int x )
: Base< T >::Nested( x ) // 멤버 초기화 리스트에 있는 기본 클래스 식별자. typename을 쓰면 안 됨
{
typename Base< T >::Nested temp; // 중첩 의존 타입 이름이며 기본 클래스 리스트에도 없고
... // 멤버 초기화 리스트의 기본 클래스 식별자도 아님. typename 필요
}
};
typename은 중첩 의존 타입 이름 앞에 붙여야 한다는 규칙에 예외가 있는데, 중첩 의존 타입 이름이 기본 클래스의 리스트에 있거나 멤버 초기화 리스트 내의 기본 클래스 식별자로서 있을 경우에는 typename을 붙여 주면 안 된다는 것이다.
template< typename T >
void WorkWithIterator( T iter )
{
typedef typename std::iterator_traits< T >::value_type value_type;
value_type temp( *iter );
...
}
std::iterator_traits< T >::value_type은 중첩 의존 타입 이름이므로(value_type이 iterator_traits< T > 안에 중첩되어 있고, T는 템플릿 매개변수이다), 이 이름 앞에는 typename을 써 주어야 한다.
이것만은 잊지 말자!
템플릿 매개변수를 선언할 때, class 및 typename은 서로 바꾸어 써도 무방하다.
중첩 의존 타입 이름을 식별하는 용도에는 반드시 typename을 사용한다. 단, 중첩 의존 이름이 기본 클래스 리스트에 있거나 멤버 초기화 리스트 내의 기본 클래스 식별자로 있는 경우에는 예외이다.
출처 : https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=ktm0122&logNo=20167641378
'프로그래밍 언어 > C++' 카테고리의 다른 글
C++ 예외 처리용 throw(), noexcept() (0) | 2022.06.16 |
---|---|
C++ 전방 선언 (Forward Declaration) (0) | 2022.06.16 |
C++ r-value && (임시 객체) / l-value & (고유 객체, 주소값) (0) | 2022.06.15 |
C++ RTTI 그리고 vtable(가상 함수 테이블) (0) | 2022.06.14 |
C++ 출력(std::format)과 for-range loop 꿀팁 (0) | 2022.06.14 |