스토리텔링 개발자

[Effective C++] 42. typename의 두 가지 용법 본문

개발/Effective C++

[Effective C++] 42. typename의 두 가지 용법

김디트 2024. 7. 12. 11:08
728x90

항목 42 : typename의 두 가지 의미를 제대로 파악하자

 

 

 

템플릿에서 class와 typename의 차이
template<class T> class Widget; // class 키워드를 사용한다.
template<typename T> class Widget; // typename 키워드를 사용한다.
  • 이 경우 이 둘은 사실상 완전히 같은 의미를 가진다.
  • 하지만 typename을 쓰지 않으면 안 되는 경우가 있다.

 

 

 

템플릿 안에서 참조할 수 있는 두 종류의 이름
// 컨테이너의 두 번째 원소를 출력한다.
// 하지만 문제가 있는 코드!!
tmeplate<typename C>
void print2nd(const C& container)
{
    if(container.size() >= 2)
    {
        C::const_iterator iter(container.begin()); // iter : 의존 이름
        ++iter;
        int value = *iter; // value : 비의존 이름
        std::cout << value;
    }
}
  • 의존 이름(dependent name)
    • 템플릿 매개변수에 종속적이다.
    • 의존 이름이 어떤 클래스 안에 중첩되어 있는 경우엔 중첩 의존 이름(nested dependent name)이라고 부른다.
      • 위의 경우 C::const_iterator는 중첩 의존 이름이다.
      • C가 의존 이름이고, const_iterator 역시 의존 이름이기 때문이다.
  • 비의존 이름(non-dependent name)
    • 템플릿 매개변수와 관계 없는 타입 이름

 

 

 

중첩 의존 이름의 모호함
template<typename C>
void print2nd(const C& container)
{
    // C::const_iterator가 타입이라 가정하지만...
    // C::const_iterator 라는 정적 데이터 멤버가 존재한다면?
    // 정적 데이터 멤버와 x를 피연산자로 한 곱셈 연산으로 인식할 것이다.
    C::const_iterator* x;
    ...
}
  • C의 정체가 무엇인지 다른 곳에서 알려주지 않으면, C::const_iterator가 진짜 타입인지 아닌지 컴파일러가 알아낼 방법은 없다.
  • 이 경우 C++는 모호성을 해결하기 위해 아래 규칙에 따라 행동한다.
    • 구문 분석기는 템플릿 안에서 중첩 의존 이름을 만나면 프로그래머가 타입이라고 알려주지 않는 한 그 이름이 타입이 아닌 것으로 해석한다.
    • 다시 말해, 중첩 의존 이름은 기본적으로 타입이 아닌 것으로 해석된다.
      template<typename C>
      void print2nd(const C& container)
      {
          if(container.size() >= 2)
          {
              // C::const_iterator를 타입이 아닌 것으로 가정한다!!!!
              C::const_iterator iter(container.begin());
              ...
          }
      }

 

 

 

중첩 의존 이름이 타입임을 명시하기
template<taypename C>
void print2nd(const C& container)
{
    if(container.size() >= 2)
    {
        // 이제 컴파일러는 C::const_iterator를 정상적으로 타입으로 이해한다.
        typename C::const_iterator iter(container.begin());
        ...
    }
}
  • typename 키워드를 사용하여 타입임을 명시한다.
  • 하지만, typename 키워드는 중첩 의존 이름을 식별할때만 써야 한다.
    template<typename C>                // typename을 쓸 수 있다.(class를 써도 된다.)
    void f(const C& container,          // typename 쓰면 안된다. 
          typename C::iterator iter);   // typename 써야 한다.
  • C는 중첩 의존 타입 이름이 아니기 때문에 typename 키워드를 붙이면 안된다.(컴파일 에러가 발생한다.)

C::iterator에 typename을 붙이지 않은 경우
C에 typename을 붙인 경우

 

 

 

typename은 중첩 의존 타입 이름 앞에만 붙여야 한다 규칙의 예외 상황
  • 중첩 의존 타입 이름이 다음 상황일 때는 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에 대한 또 다른 예
tmpelate<typename IterT>
void workWithIterator(IterT iter)
{
    // 매개변수로 넘어온 반복자가 가리키는 객체의 사본을 복사해준다.
    // 이 경우, std::iterator_traits<IterT>::value_type은 중첩 의존 타입이다.
    // value_type이 iterator_traits<IterT> 안에 중첩되어 있고,
    // IterT는 템플릿 매개변수이므로.
    typename std::iterator_traits<IterT>::value_type temp(*iter);
    ...
}
  • std::iterator_traits<IterT>::value_type에 관한 것은 항목 47 참조
  • 여기서 std::iterator_traits<IterT>::value_type이 너무 기니까 typedef를 해보면..
template<typename IterT>
void workWithIterator(IterT iter)
{
    typedef typename std::iterator_traits<IterT>::value_type value_type;
    
    // 반복자가 가리키는 객체의 사본 생성
    value_type temp(*iter);
    ...
}
  • typedef typename 이 뭔가 잘못된 것 같아 보여도, 합법한 문법이다.

 

 

 

주의점
  • typename으로 타입을 명시하지 않았을 때 컴파일 에러 여부는 컴파일러 의존적일 수 있다.
  • 즉, typename을 붙이지 않고도 에러 없이 넘어갈 수 있으나, 다른 컴파일러에선 그렇지 않을 수 있다.
728x90
Comments