C++/Templates 2021. 3. 22. 00:07

멤버 함수를 한 번 더 템플릿화하기

  • 클래스 템플릿에 있는 멤버 함수를 또 다시 템플릿화할 수 있다.

예제

  #include <iostream>

  using namespace std;

  template <typename T>
  class A
  {
    T    value_;

  public:
    A(const T & input)
      : value_(input)
    {}

    template <typename TT>
    void doSomething()
    {
      cout << typeid(T).name() << " " << typeid(TT).name() << endl;
    }

    void print()
    {
      cout << value_ << endl;
    }
  };

  int            main()
  {
    A<int> a_int(123);
    a_int.print();
    a_int.doSomething<float>();
  }

  /* stdout
  123
  int float
  */

응용

  • char 타입을 int 타입으로 형변환하여 출력하기

    • 함수 템플릿은 함수명 옆에 <int>를 명시하는 대신 인자로 int 타입의 값을 넣으면 파라미터가 알아서 들어간다.
    #include <iostream>
    
    using namespace std;
    
    template <typename T>
    class A
    {
      T    value_;
    
    public:
      A(const T & input)
        : value_(input)
      {}
    
      template <typename TT>
      void doSomething(const TT& input)
      {
        cout << typeid(T).name() << " to " << typeid(TT).name() << endl;
        cout << (TT)value_ << endl;
      }
    
      void print()
      {
        cout << value_ << endl;
      }
    };
    
    int            main()
    {
      A<char> a_char('A');
      a_char.print();
      a_char.doSomething(int());
    }
    
    /* stdout
    A
    char to int
    65
    */
C++/Templates 2021. 3. 22. 00:05

포인터 템플릿 특수화(Pointer Templates Specialization)

  • 템플릿 파라미터가 포인터인 경우 특수화를 하고 싶다면 클래스명에 <T*>를 붙여서 작성한다.

예제

  #include <iostream>

  using namespace std;

  template <typename T>
  class A
  {
    T    value_;

  public:
    A(const T & input)
      : value_(input)
    {}

    void print()
    {
      cout << value_ << endl;
    }
  };

  template <typename T>
  class A<T*>
  {
    T*    value_;

  public:
    A(T* input)
      : value_(input)
    {}

    void print()
    {
      cout << *value_ << endl;
    }
  };

  int            main()
  {
    A<int> a_int(123);
    a_int.print();

    int tmp = 456;

    A<int*> a_int_ptr(&tmp);
    a_int_ptr.print();

    double tmp_d = 3.141592;
    A<double*> a_double_ptr(&tmp_d);
    a_double_ptr.print();
  }

  /* stdout
  123
  456
  3.14159
  */
C++/Templates 2021. 3. 22. 00:04

템플릿 부분 특수화 (Templates Partial Specialization)

  • 템플릿에서 여러 파라미터를 받을 때, 특정 파라미터만 특수화할 수 있다.

예제

일반 함수 템플릿 부분 특수화

  #include <iostream>

  using namespace std;

  template <typename T, int size>
  class StaticArray
  {
  private:
    T    array_[size];

  public:
    T*    getArray() { return array_; }
    T&    operator[](int index)
    {
      return array_[index];
    }
  };

  template <typename T, int size>
  void        print(StaticArray<T, size>& array)
  {
    for (int count = 0; count < size; ++count)
      cout << array[count] << ' ';
    cout << endl;
  }

  template <int size>
  void        print(StaticArray<char, size>& array)
  {
    for (int count = 0; count < size; ++count)
      cout << array[count];
    cout << endl;
  }

  int            main()
  {
    StaticArray<int, 4>    int4;

    int4[0] = 1;
    int4[1] = 2;
    int4[2] = 3;
    int4[3] = 4;

    print(int4);

    StaticArray<char, 14> char14;

    strcpy_s(char14.getArray(), 14, "Hello, World!");

    print(char14);
  }

  /* stdout
  1 2 3 4
  Hello, World!
  */

템플릿 클래스의 멤버 함수 부분 특수화

  • 상속을 이용한다.

    • 기존의 클래스 뒤에 _BASE를 붙이고, 이를 상속하는 클래스를 원래 이름으로 만든다.

    • 클래스 템플릿 특수화를 사용하여 특수화를 하고 싶은 부분만 구현해주면 된다.

    #include <iostream>
    
    using namespace std;
    
    template <typename T, int size>
    class StaticArray_BASE
    {
    private:
      T    array_[size];
    
    public:
      T*    getArray() { return array_; }
      T&    operator[](int index)
      {
        return array_[index];
      }
    
      void    print()
      {
        for (int count = 0; count < size; ++count)
          cout << (*this)[count] << ' ';
        cout << endl;
      }
    };
    
    template <typename T, int size>
    class StaticArray : public StaticArray_BASE<T, size>
    {
    };
    
    template <int size>
    class StaticArray<char, size> : public StaticArray_BASE<char, size>
    {
    public:
      void    print()
      {
        for (int count = 0; count < size; ++count)
          cout << (*this)[count];
        cout << endl;
      }
    };
    
    int            main()
    {
      StaticArray<int, 4>    int4;
    
      int4[0] = 1;
      int4[1] = 2;
      int4[2] = 3;
      int4[3] = 4;
    
      int4.print();
    
      StaticArray<char, 14> char14;
    
      strcpy_s(char14.getArray(), 14, "Hello, World!");
    
      char14.print();
    }
    
    /* stdout
    1 2 3 4
    Hello, World!
    */
C++/Templates 2021. 3. 22. 00:02

클래스 템플릿 특수화 (Class Templates Specialization)

  • 템플릿의 특정 자료형에 대해서 다르게 처리하고 싶을 때 사용한다.

  • 함수 템플릿 특수화에서는 template<>을 명시하지 않아도 작동이 됐는데, 클래스 템플릿 특수화에서는 명시하지 않으면 오류가 발생한다.

    error C2906: 'A<char>': explicit specialization requires 'template <>'

예제

  • 클래스 템플릿 특수화는 다른 클래스를 하나 따로 만들었다고 생각해야 한다.

    • 아래의 예제에서 a_char.test 함수는 존재하지 않는다. 상속과는 엄연히 다르다.
    #include "MyArray.h"
    
    using namespace std;
    
    template<typename T>
    class A
    {
    public:
      void doSomething()
      {
        cout << typeid(T).name() << endl;
      }
    
      void test() {}
    };
    
    template<>
    class A<char>
    {
    public:
      void doSomething()
      {
        cout << "Char type specialization\n";
      }
    };
    
    int            main()
    {
      A<int>        a_int;
      A<double>    a_double;
      A<char>        a_char;
    
      a_int.doSomething();
      a_double.doSomething();
      a_char.doSomething();
    }
    
    /* stdout
    int
    double
    Char type specialization
    */

비트마스크를 통한 메모리 절약 예제

  • 아래 예제에서 bool 자료형에 대해 클래스 템플릿 특수화를 하지 않는다면, Storage8 인스턴스는 8바이트의 크기를 차지하게 된다.

  • 대신 bool 자료형일 경우 비트마스크를 이용하여 메모리를 1/8 수준으로 절약할 수 있다.

    #include <iostream>
    
    using namespace std;
    
    template<typename T>
    class Storage8
    {
      T    array_[8];
    
    public:
      void        set(int index, const T& value)
      {
        array_[index] = value;
      }
    
      const T&    get(int index)
      {
        return array_[index];
      }
    };
    
    template<>
    class Storage8<bool>
    {
      unsigned char    data_;
    
    public:
      Storage8() : data_(0)
      {}
    
      void    set(int index, bool value)
      {
        unsigned char mask = 1 << index;
    
        if (value)
          data_ |= mask;
        else
          data_ &= ~mask;
      }
    
      bool    get(int index)
      {
        unsigned char mask = 1 << index;
        return ((data_ & mask) != 0);
      }
    };
    
    int            main()
    {
      Storage8<int> intStorage;
    
      for (int count = 0; count < 8; ++count)
        intStorage.set(count, count);
      for (int count = 0; count < 8; ++count)
        cout << intStorage.get(count) << '\n';
      cout << "Sizeof Storage8<int> : " << sizeof(Storage8<int>) << endl;
    
      Storage8<bool> boolStorage;
    
      for (int count = 0; count < 8; ++count)
        boolStorage.set(count, count & 3);
      for (int count = 0; count < 8; ++count)
        cout << std::boolalpha << boolStorage.get(count) << '\n';
      cout << "Sizeof Storage8<bool> : " << sizeof(Storage8<bool>) << endl;
    }
    
    /* stdout
    0
    1
    2
    3
    4
    5
    6
    7
    Sizeof Storage8<int> : 32
    false
    true
    true
    true
    false
    true
    true
    true
    Sizeof Storage8<bool> : 1
    */
C++/Templates 2021. 3. 22. 00:00

함수 템플릿 특수화 (Function Templates Specialization)

  • 템플릿이 있는 상태에서, 특정 자료형에 대해 다른 코드를 실행하고 싶을 때 사용한다.

  • explicit instantiation과 비슷한 느낌인데, 이렇게 정의된 함수는 템플릿에서 따로 인스턴스화하지 않는다고 생각하면 될 것 같다.


예제

  #include "MyArray.h"

  using namespace std;

  template<typename T>
  T            getMax(T x, T y)
  {
    return (x > y) ? x : y;
  }

  template<>
  char        getMax(char x, char y)
  {
    cout << "Warning : comparing chars\n";
    return (x > y) ? x : y;
  }

  int            main()
  {
    cout << getMax(1, 2) << endl;
    cout << getMax('a', 'b') << endl;
  }

  /* stdout
  2
  Warning : comparing chars
  b
  */

  • 템플릿 특수화 코드는 보통 헤더에 같이 집어넣는 듯 하다.

    • .cpp 파일로 분리하면 적용하기가 번거롭기 때문이다.
  • 앞에 template<> 키워드는 없어도 작동이 되는데, 템플릿 특수화를 표시하기 위해 쓰는 것 같다.

C++/Templates 2021. 3. 21. 23:59

자료형이 아닌 템플릿 매개변수 (Non-type Templates Parameters)

  • 템플릿 매개변수는 컴파일 타임에 결정되어야 한다.

    • 템플릿의 원리가 여러 경우에 대해 컴파일을 따로 해주는 것이기 때문이다.
  • Non-type 파라미터를 사용할 경우 헤더에서 함수를 정의한다.

    • template<typename T, unsigned int T_SIZE>와 같이 unsigned int가 온다고 생각해보자.

    • 모든 숫자에 대해 explicit instantiation을 적용할 수는 없으므로 헤더에서 정의하는 것이다.


예제

main.cpp

  #include "MyArray.h"

  int            main()
  {
    MyArray<double, 10> my_array;

    for (int i = 0; i < my_array.getLength(); ++i)
      my_array[i] = i * 0.5;

    my_array.print();
  }

  /* stdout
  0 0.5 1 1.5 2 2.5 3 3.5 4 4.5
  */

MyArray.h

  #pragma once
  #include <iostream>
  #include <cassert>

  template<typename T, unsigned int T_SIZE>
  class MyArray
  {
    T*        data_;

  public:
    MyArray()
    {
      data_ = new T[T_SIZE];
    }

    ~MyArray()
    {
      delete[] data_;
    }

    T& operator[](int index)
    {
      assert(index >= 0 && index < T_SIZE);
      return data_[index];
    }

    int getLength()
    {
      return T_SIZE;
    }

    void print()
    {
      for (int i = 0; i < T_SIZE; ++i)
        std::cout << data_[i] << ' ';
      std::cout << std::endl;
    }
  };
C++/Templates 2021. 3. 21. 23:59

클래스 템플릿 (Class Templates)

  • 클래스에도 템플릿을 적용할 수 있다.

예제

  • 템플릿을 사용하지 않은 예제

    main.cpp

    #include "MyArray.h"
    
    int            main()
    {
      MyArray my_array(10);
    
      for (int i = 0; i < my_array.getLength(); ++i)
        my_array[i] = i * 10;
    
      my_array.print();
    }
    
    /* stdout
    0 10 20 30 40 50 60 70 80 90
    */

    MyArray.h

    #pragma once
    #include <iostream>
    #include <cassert>
    
    class MyArray
    {
      int  length_;
      int* data_;
    
    public:
      MyArray()
      {
        length_ = 0;
        data_ = nullptr;
      }
    
      MyArray(const int& length_in)
      {
        data_ = new int[length_in];
        length_ = length_in;
      }
    
      ~MyArray()
      {
        reset();
      }
    
      void reset()
      {
        delete[] data_;
        data_ = nullptr;
        length_ = 0;
      }
    
      int& operator[](int index)
      {
        assert(index >= 0 && index < length_);
        return data_[index];
      }
    
      int getLength()
      {
        return length_;
      }
    
      void print()
      {
        for (int i = 0; i < length_; ++i)
          std::cout << data_[i] << ' ';
        std::cout << std::endl;
      }
    };

  • 템플릿을 적용한 예제

    main.cpp

    #include "MyArray.h"
    
    int            main()
    {
      MyArray<int> my_array(10);
    
      for (int i = 0; i < my_array.getLength(); ++i)
        my_array[i] = i * 10;
    
      my_array.print();
    }
    
    /* stdout
    0 10 20 30 40 50 60 70 80 90
    */

    MyArray.h

    #pragma once
    #include <iostream>
    #include <cassert>
    
    template<typename T>
    class MyArray
    {
      int        length_;
      T*        data_;
    
    public:
      MyArray()
      {
        length_ = 0;
        data_ = nullptr;
      }
    
      MyArray(const int& length_in)
      {
        data_ = new T[length_in];
        length_ = length_in;
      }
    
      ~MyArray()
      {
        reset();
      }
    
      void reset()
      {
        delete[] data_;
        data_ = nullptr;
        length_ = 0;
      }
    
      T& operator[](int index)
      {
        assert(index >= 0 && index < length_);
        return data_[index];
      }
    
      int getLength()
      {
        return length_;
      }
    
      void print()
      {
        for (int i = 0; i < length_; ++i)
          std::cout << data_[i] << ' ';
        std::cout << std::endl;
      }
    };
    • main.cpp 에서 다른 자료형을 넣어도 작동한다.

      #include "MyArray.h"
      
      int            main()
      {
        MyArray<double> my_array(10);
      
        for (int i = 0; i < my_array.getLength(); ++i)
          my_array[i] = i * 0.5;
      
        my_array.print();
      }
      
      /* stdout
      0 0.5 1 1.5 2 2.5 3 3.5 4 4.5
      */
      #include "MyArray.h"
      
      int            main()
      {
        MyArray<char> my_array(10);
      
        for (int i = 0; i < my_array.getLength(); ++i)
          my_array[i] = i + 65;
      
        my_array.print();
      }
      
      /* stdout
      A B C D E F G H I J
      */

헤더 파일과 .cpp 파일의 분리

  • 헤더에 함수를 정의했는데, 이를 .cpp 파일로 분리해야 한다.

  • 템플릿을 사용하면 이 과정이 살짝 번거롭다.

  • 일단 헤더 파일에서만 print 함수를 분리해보면 다음과 같다.

    MyArray.h

    #pragma once
    #include <iostream>
    #include <cassert>
    
    template<typename T>
    class MyArray
    {
      int        length_;
      T*        data_;
    
    public:
      MyArray()
      {
        length_ = 0;
        data_ = nullptr;
      }
    
      MyArray(const int& length_in)
      {
        data_ = new T[length_in];
        length_ = length_in;
      }
    
      ~MyArray()
      {
        reset();
      }
    
      void reset()
      {
        delete[] data_;
        data_ = nullptr;
        length_ = 0;
      }
    
      T& operator[](int index)
      {
        assert(index >= 0 && index < length_);
        return data_[index];
      }
    
      int getLength()
      {
        return length_;
      }
    
      void print();
    };
    
    template<typename T>
    void MyArray<T>::print()
    {
      for (int i = 0; i < length_; ++i)
        std::cout << data_[i] << ' ';
      std::cout << std::endl;
    }
    • 이대로 컴파일하면 아무 문제가 없다.
  • 다음은 MyArray.cpp 파일로 옮기는 과정이다.

    • 헤더 파일에서 분리한 함수 정의 부분을 잘라내서, .cpp 파일에 그대로 붙여넣기한다.

      MyArray.cpp

      #include "MyArray.h"
      
      template<typename T>
      void MyArray<T>::print()
      {
        for (int i = 0; i < length_; ++i)
          std::cout << data_[i] << ' ';
        std::cout << std::endl;
      }
      error LNK2019: unresolved external symbol "public: void __cdecl MyArray<char>::print(void)" (?print@?$MyArray@D@@QEAAXXZ) referenced in function main
      • main.cpp에서 MyArray.cpp에 정의된 함수의 body 내용을 알 수 없기 때문에 발생하는 문제라고 한다.

        • 템플릿의 작동 원리를 알아야 완벽하게 파악할 수 있을 것 같다.
      • main.cpp에서 MyArray.cppinclude 하면 해결되긴 하는데, 이렇게 하는 것은 좋지 않다고 한다.

      • 대신 explicit instantiation을 해야하는데, 명시적으로 템플릿을 적용하는 것이다.


Explicit Instantiation

  • 클래스나 함수에 적용할 템플릿을 명시해주는 것이다.

    MyArray.cpp

    #include "MyArray.h"
    
    template<typename T>
    void MyArray<T>::print()
    {
      for (int i = 0; i < length_; ++i)
        std::cout << data_[i] << ' ';
      std::cout << std::endl;
    }
    
    template class MyArray<char>;
    template void MyArray<double>::print();
C++/Templates 2021. 3. 21. 23:58

함수 템플릿 (Function Templates)

  • template<typename T>, template<class T>의 형태로 템플릿을 선언할 수 있다.

예제

  #include <iostream>

  template<typename T>
  T    getMax(T x, T y)
  {
    return (x > y) ? x : y;
  }

  class Cents
  {
      int    cents_;

  public:
      Cents(int cents = 0) { cents_ = cents; }

      bool    operator > (const Cents& c)
      {
          return (cents_ > c.cents_);
      }

      friend std::ostream& operator << (std::ostream& out, const Cents& cents)
      {
          out << cents.cents_ << " cents";
          return (out);
      }
  };

  int        main()
  {
    using namespace std;

    cout << getMax(1, 2) << endl;
    cout << getMax(3.14, 1.592) << endl;
    cout << getMax(1.0f, 3.4f) << endl;
    cout << getMax('a', 'c') << endl;
    cout << getMax(Cents(5), Cents(9)) << endl;
  }

  /* stdout
  2
  3.14
  3.4
  c
  9 cents
  */
C++/Templates 2021. 3. 21. 23:57

템플릿 (Templates)

함수 템플릿 (Function Templates)

클래스 템플릿 (Class Template)

자료형이 아닌 템플릿 매개변수 (Non-type Templates Parameters)

함수 템플릿 특수화 (Function Templates Specialization)

클래스 템플릿 특수화 (Class Templates Specialization)

템플릿 부분 특수화 (Templates Partial Specialization)

포인터 템플릿 특수화(Pointer Templates Specialization)

멤버 함수를 한 번 더 템플릿화하기