[C++] 항목 42: typename의 두 가지 의미를 제대로 파악하자
카테고리: Cpp
태그: Cpp
이 글은 아래의 책을 자세히 정리한 후, 정리한 글을 GPT에게 요약을 요청하여 작성되었습니다.
이펙티브 C++ 제3판, 스콧 마이어스 저자, 곽용재 번역
📦 7. 템플릿과 일반화 프로그래밍
👉🏻 항목 42: typename의 두 가지 의미를 제대로 파악하자
✅ typename의 첫 번째 의미: class와 동일
// 1번
template<class T>
class Widget;
// 2번
template<typename T>
class Widget;
핵심:
- 위 두 코드는 차이가 없다
 - 템플릿 매개변수를 선언할 때, 
class와typename은 서로 바꾸어 써도 된다 
그러나, typename이 반드시 쓰여야 하는 경우가 있다.
✅ 의존 이름과 비의존 이름
// 제대로 된 코드는 아니다.
template<typename C>
// 컨테이너에 있는 두 번째 원소를 출력하는 함수
void print2nd(const C& container) {
	if(container.size() >= 2) {
		C::const_iterator iter(container.begin());
		++iter;
		int value = *iter;
		cout << value;
	}
}
의존 이름 (Dependent Name)
C::const_iterator
- 템플릿 매개변수 C에 따라 달라지는 타입이다
 - 템플릿 매개변수에 종속된 것을 의존 이름이라고 한다
 
비의존 이름 (Non-dependent Name)
int
int와 같이 템플릿 매개변수와 상관없는 타입 이름을 비의존 이름이라 한다
📌 의존 이름의 동작
int main() {
	vector<int> v = {1,2,3};
	print2nd(v);
	list<int> l = {1,2,3};
	print2nd(l);
}
의존 이름의 변환:
C::const_iterator는 함수 내에서 각각 아래와 같은 타입 이름으로 변환될 것이다vector<int>::const_iteratorlist<int>::const_iterator
- 이것이 의존 이름이다
 
중첩 의존 (타입) 이름
이름이 어떤 클래스 안에 중첩되어 있는 것
⚠️ 문제: 컴파일러의 모호성
struct Foo {
	static int const_iterator;
};
template<typename C>
void print2nd(const C& container) {
	// 이것이 곱셈 연산일 가능성이 생긴다.
	C::const_iterator* x;
	...
}
문제:
- 타입 이름으로 바뀔 것이라 장담할 수 없다
 struct Foo { static int const_iterator; }같은 경우로 인해 컴파일러는,
C::const_iterator가 변수인지 타입인지 알 수 없게 된다
컴파일러 입장:
C::const_iterator* x;가 타입 선언인가?- 아니면 
C::const_iterator와x의 곱셈 연산인가? 
✅ 해결: typename으로 타입 명시
template<typename C>
void print2nd(const C& container) {
	if(container.size() >= 2) {
		typename C::const_iterator iter(container.begin());
		...
	}
}
규칙:
템플릿 안에서 중첩 의존 이름을 참조할 경우, typename 키워드를 붙여주자.
🚨 typename 사용 시 주의점
예시 1: 함수 매개변수
template<typename C>
void f(
	const C& container,          // ❌ typename 쓰면 안 됨
	typename C::iterator iter    // ✅ typename 써야 함
);
규칙:
중첩 의존 이름을 식별하는 데만 typename을 사용하여야 한다.
예시 2: 기본 클래스와 멤버 초기화 리스트
template<typename T>
// 상속되는 기본 클래스 리스트
// ↓ ❌ typename 쓰면 안 됨
class Derived: public Base<T>::Nested {
public:
	// 멤버 초기화 리스트의 기본 클래스 식별자
	// ↓ ❌ typename 쓰면 안 됨
	explicit Derived(int x)
	: Base<T>::Nested(x) {
		// 기본 클래스 리스트에 없음
		// 멤버 초기화 리스트의 기본 클래스 식별자 아님
		// ↓ ✅ typename 필요
		typename Base<T>::Nested temp;
		...
	}
	...
};
중첩 의존 타입 이름이지만, typename을 붙이면 안 되는 경우:
- 기본 클래스의 리스트에 있을 경우
 - 멤버 초기화 리스트 내의 기본 클래스 식별자로 있을 경우
 
이 외에는 typename이 필요하다.
📊 typename 사용 여부 정리
| 위치 | typename 사용 여부 | 예시 | 
|---|---|---|
| 템플릿 매개변수 | 선택 (class와 동일) | template<typename T> | 
    
| 중첩 의존 타입 이름 | 필수 | typename C::iterator | 
    
| 기본 클래스 리스트 | 사용 금지 | class D: public Base<T>::Nested | 
    
| 멤버 초기화 리스트 | 사용 금지 | : Base<T>::Nested(x) | 
    
| 함수 본문 내 | 필요 시 사용 | typename Base<T>::Nested temp; | 
    
✅ 실용 예제 1: iterator_traits
template<typename IterT>
void workWithIterator(IterT iter) {
	typename iterator_traits<IterT>::value_type temp(*iter);
	...
}
코드 분석:
iterator_traits는 “반복자가 어떤 종류의 반복자인지”를
일반적으로 다룰 수 있게 해주는 도구이다iterator_traits<IterT>: IterT 타입 특성 정보를 다룸::value_type: IterT의 타입 추론temp(*iter): iter의 타입을 temp에 넣음
예시:
iter가vector<int>::iterator라면temp의 타입은int가 될 것이다
📌 iterator_traits의 내부 구조
struct iterator_traits {
	typedef typename Iterator::iterator_category iterator_category;
	typedef typename Iterator::value_type value_type;
	typedef typename Iterator::difference_type difference_type;
	typedef difference_type distance_type;
	typedef typename Iterator::pointer pointer;
	typedef typename Iterator::reference reference;
};
핵심 개념:
iterator_traits는 타입 특성 정보를 일반화해 다루는 메커니즘이다- Java의 Reflection과 유사하다
    
obj.getClass().getName()같이 정보를 추출할 때 쓰는 점!
 
✅ 실용 예제 2: typedef로 간결하게
template<typename IterT>
void workWithIterator(IterT iter) {
	typedef typename iterator_traits<IterT>::value_type value_type;
	value_type temp(*iter);
	...
}
장점:
- 긴 타입 이름을 typedef로 정의하여 코드 가독성 향상
 typename은 typedef 선언 시 한 번만 사용
💡 실전 활용 팁
긴 중첩 의존 타입은 typedef로 간소화:
// ❌ 읽기 어려움
template<typename T>
void process(T container) {
	typename std::vector<typename T::value_type>::iterator it;
	...
}
// ✅ 읽기 쉬움
template<typename T>
void process(T container) {
	typedef typename T::value_type ValueType;
	typedef typename std::vector<ValueType>::iterator Iterator;
	Iterator it;
	...
}
🧐 정리
- 템플릿 매개변수를 선언할 때, class와 typename은 서로 바꾸어 써도 된다.
의미상 차이가 없다. - 중첩 의존 타입 이름을 식별하는 용도는 반드시 typename을 사용한다.
컴파일러가 타입임을 인식하도록 해야 한다. - 중첩 의존 이름을 typename 없이 사용하는 경우:
    
- 중첩 의존 이름이 기본 클래스 리스트에 있을 때
 - 멤버 초기화 리스트 내의 기본 클래스 식별자로 있을 때
 
 - 긴 중첩 의존 타입 이름은 typedef로 간소화하여 가독성을 높이자.
한 번만 typename을 사용하고 이후에는 typedef 이름을 사용할 수 있다. 
댓글남기기