템플릿과 일반화 프로그래밍 📏

안녕하세요! 두두코딩 입니다 ✋
오늘은 Effective C++ 항목 42에 대해 알아보겠습니다.

🖇 소스코드에 마우스를 올리고 copy 버튼을 누를 경우 더 쉽게 복사할 수 있습니다!

궁금한 점, 보안점 남겨주시면 성실히 답변하겠습니다. 😁
+ 감상평 댓글로 남겨주시면 힘이됩니다. 🙇

Template에서 typename 과 class

Q. 아래의 두 템플릿 선언문에 쓰인 class 와 typename의 차이점은 무엇일까?
template<class T> T Widget;
template<typename T> T Widget;

답변은 차이가 없다이다. 템플릿의 타입 매개변수를 선언할 때, typename 과 class의 뜻은 완전히 동일하다.

개인차이로 타입 매개변수를 선언하는 것을 선정해도 무방하다. 하지만, typename을 꼭 사용해야될 때가 있는데, 그 경우를 알아보자.

typename을 사용해야되는 경우

아래와 같이 함수의 템플릿이 있다고 생각해보자.

해당 함수 같은 경우 2가지 특징이 있다.

  1. STL과 호환되는 컨테이너를 받아들이도록 만들어졌다.
  2. 컨테이너에 담기는 객체는 int로 호환이 가능하다.
template<typename C>
void print2nd(const C& container) {
  if (container.size() >= 2) {
    // 첫번째 원소에 대한 반복자를 얻음
    C::const_iterator iter(container.begin());

    ++iter;
    // int에 두번째 원소를 복사
    int value = *iter;
    std::cout << value;
  }
}

iter 타입 같은 경우, C::const_iterator 타입으로 C의 매개변수로 인해 타입이 달라지도록 설계되어져있다. 이와 같은 경우를 의존이름이라고 부른다. 의존이름이 어떤 클래스 내 중첩되어져 있는 경우를 중첩 의존 이름이라고 부른다.

템플릿 매개변수와 상관없는 변수는 비의존 이름이라고 부른다.

중첩이름이 코드 내 있을 경우 에러가 발생할 확률이 올라간다.

template <typename C>
void print2nd(const C& container) {
  C::const_iterator * x;
  ...
}

위 코드를 통해 어떤 에러가 발생하는지 알아보자.

우리는 코드를 봤을 때, C::const_iterator에 대한 포인터를 지역변수 x로 선언했다는 것을 알 수 있다. 하지만 컴파일러 입장에서는 “* 포인터” 라고 생각할 수도 있고, C 라는 class 내 const_iterator 멤버 변수와 x 라는 전역변수의 “* 곱셈연산”으로 해당 문장을 해석할 수도 있다.

따라서 이를 막기 위해, typename 이라는 키워드를 해당 값 앞에 붙여줘 C 라는 매개변수가 있다는 것을 컴파일러에게 알려준다. 즉, 해당 값은 데이터가 아니라 타입이라는 것을 알려줘야한다.

template <typename C>
void print2nd(const C& container) {
  typename C::const_iterator * x;
  ...
}

위의 코드와 같이 중첩 의존 이름 앞에는 항상 typename을 적어줘야한다. 하지만, typename을 사용함에서도 예외가 몇가지 존재하는데, 예외를 알아보자.

typename 예외

“typename은 중첩 의존 타입 이름 앞에 붙여 주어야 한다”는 규칙에는 2가지 예외가 존재한다.

  1. 상속되는 기본클래스의 리스트에 있을 경우
  2. 초기화 리스트 내에 기본 클래스 식별자 일 경우
template<typename T>
class Derived: public Base<T>::Nested {   //상속되는 기본클래스 리스트, typename X
public :
  explicit Derived(int x) : Base<T>::Nested(x) { //초기화리스트내에 기본클래스 식별자, typename X
    typename Base<T>::Nested temp;  // 중첩의존타입의 이름, typename O
    
  }
  
};

해당 케이스 같은 경우 typename을 붙이면 안된다는 것을 알아두도록 하자.

typedef 를 활용한 typename

template을 활용한 코드 같은 경우 typename이라는 키워드를 붙이고 사용한다면 너무 길어질 수 있다. 예를 들어, 아래의 iterator_traits를 활용하는 코드가 있다고 가정해보자.

template<typename IterT>
void workWithIterator(IterT iter)
{
  typename std::iterator_traits<IterT>::value_type temp(*iter);
  ...
}

위와 같은 코드가 있을 때, value_type을 코드에서 계속 사용하기 위해서는 typename std::iterator_traits<IterT>::을 지속적으로 적어줘야한다. 이는 코드 작성 시 에러를 유발할 수 있기 때문에 typedef를 활용해 해당 값을 대체하도록 하는 것이 좋다.

template<typename IterT>
void workWithIterator(IterT iter)
{
  typedef typename std::iterator_traits<IterT>::value_type value_type;

  value_type temp(*iter);

  ...
}

To Sum Up