반응형

템플릿 특수화(Template specialization), 부분 특수화(Partial specialization) 개념

  • 템플릿의 특정 패턴에 대해서 별도의 처리가 하고 싶을 경우 부분 특수화 또는 특수화를 이용할 수 있음
  • 메인 템플릿의 인자가 2개라면, 사용자는 반드시 템플릿 인자를 2개 전달해야 한다.(기본값이 없다면)
  • 부분 특수화(Partial specialization) 템플릿에서는 인자의 수는 메인 템플릿 인자수와 다를 수 있다.
  • 특수화를 많이 할 경우 소스코드가 늘어날 수는 있지만 결국 목적코드로 생성되는 기계어 코드의 양은 같음
#include <iostream>
using namespace std;

// 메인 템플릿(Primary template)
// 기본적으로 모든 패턴은 메인 템플릿이 처리함
template<typename T> class stack
{
public:
    void push(T a) { cout << "T" << endl; }
};

// 부분 특수화(Partial specialization)
// 모든 포인터 타입에 대해서 별도로 처리하고 싶을 경우 부분 특수화를 적용할 수 있음
template<typename T> class stack<T*>
{
public:
    void push(T* a) { cout << "T*" << endl; }
};

// 특수화(Speicalization)
// char포인터에 대해서만 별도로 처리하고 싶을 경우 특수화를 적용할 수 있음
template<> class stack<char*>
{
public:
    void push(char* a) { cout << "char*" << endl; }
};


int main()
{
    stack<int> s1; s1.push(0); // 메인 템플릿 사용
    stack<int*> s2; s2.push(0); // 부분 특수화 템플릿 사용
    stack<char*> s3; s3.push(0); // 특수화 템플릿 사용
}

 

템플릿 특수화(specialization)/부분 특수화(Partial specialization) 예제

#include <iostream>
using namespace std;

// 1. <T, U> 메인 템플릿
template<typename T, typename U> class test
{
public:
    static void foo() { cout << "T, U" << endl; }
};

// 2. <T*, U> 부분 특수화
template<typename T, typename U> class test<T*, U>
{
public:
    static void foo() { cout << "T*, U" << endl; }
};

// 3. <T*, U*> 부분 특수화
template<typename T, typename U> class test<T*, U*>
{
public:
    static void foo() { cout << "T*, U*" << endl; }
};

// 4. <T, T> 부분 특수화
template<typename T> class test<T, T>
{
public:
    static void foo() { cout << "T, T" << endl; }
};

// 5. int, T
template<typename T> class test<int, T>
{
public:
    static void foo() { cout << "int, T" << endl; }
};

// 5. <int, T> 부분 특수화
template<> class test<int, int>
{
public:
    static void foo() { cout << "int, int" << endl; }
};

// 6. <int, short> 특수화
template<> class test<int, short>
{
public:
    static void foo() { cout << "int, short" << endl; }
};

// 7. T, test<U, V> 부분 특수화
template<typename T, typename U, typename V> 
class test<T, test<U, V>>
{
public:
    static void foo() { cout << "T, test<U, V>" << endl; }
};

int main()
{
    // 1. <T, U> 메인 템플릿
    test<int, double>::foo();

    // 2. <T*, U> 부분 특수화
    test<int*, double>::foo();

    // 3. <T*, U*> 부분 특수화
    test<int*, double*>::foo(); 

    // 4. <T, T> 부분 특수화
    test<int, int>::foo();

    // 5. <int, T> 부분 특수화
    // 인자가 <int, int>일 경우 4.<T, T> 5.<int, T> 패턴이 2개이상 일치하므로 5-1 특수화 추가 구현 필요
    test<int, char>::foo(); 

    // 6. <int, short> 특수화
    test<int, short>::foo(); 

    // 7. T, test<U, V> 부분 특수화
    test<double, test<char, short>>::foo();
}

 

템플릿 특수화(specialization)/부분 특수화(Partial specialization) 주의사항

  • 부분 특수화에서 T의 타입이 결정되는 방식을 주의해야 함
  • 부분 특수화에서 기본 파라미터는 표시하지 않음(메인 템플릿에 정의 값을 그대로 승계함)
  • 클래스의 특정 멤버 함수만 특수화 할 수 있으나 부분 특수화는 불가능
// 부분 특수화 시 T 타입의 주의
// 메인 템플릿에 포인터값이 넘어 왔을 경우 T는 포인터 타입임
template<typename T> class Test
{
public:
    static void foo() { cout << typeid(T).name() << endl; }
};

// 아래와 같이 포인터 타입으로 부분 특수화를 했을 경우 T는 포인터 타입이 아님
template<typename T> class Test<T*>
{
public:
    static void foo() { cout << typeid(T).name() << endl; }
};
// 메인 템플릿에 정의된 파라미터 기본 파라미터
template<typename T, int N = 10> class Test
{
public:
    static void foo() { cout << typeid(T).name() << endl; }
};

// 부분 특수화에서는 기본 파라미터를 표시하지 않음(메인 템플릿의 기본 파라미터 값 승계)
template<typename T, int N> class Test<T*, N>
{
public:
    static void foo() { cout << typeid(T).name() << endl; }
};
// 클래스 템플릿
template<typename T>
class Stack
{
public:
    T pop() {}
    void push(T a);
};

// 특정 멤버 함수의 특수화가 필요할 경우 구현부를 분리
template<typename T>
void Stack<T>::push(T a)
{
    cout << "T" << endl;
}

// 특정 멤버 함수의 특수화 구현(부분 특수화는 불가능함)
template<>
void Stack<char*>::push(char* a)
{
    cout << "char*" << endl;
}

int main()
{
    Stack<int> s1; s1.push(0);
    Stack<char*> s2; s2.push(0);
}

 

반응형

반응형

템플릿 파라미터의 지원 종류

타입(Type)

// 타입 파라미터
template<typename T>
class List
{

};

int main()
{
    List<int> l1; // OK
}

 

 

값(Non-type)

  • 정수형 상수
  • 열거형 상수(Enum)
  • 포인터
  • 함수 포인터
  • Auto(C++17)
#include <iostream>
using namespace std;

// 1. 정수형 상수(실수 안됨)
template<int N>
class Test1 {};

// 2. enum 상수
enum Color { red = 1, green = 2};
template<Color> class Test2 {};

// 3. 포인터 : 지역변수 주소안됨.. 전역변수주소는 가능..
// no linakge를 가지는 변수 주소는 안됨.
template<int*> class Test3 {};

// 4. 함수 포인터 : 
template<int(*)(void)> class Test4 {};

// C++17에서는 auto 타입 사용가능
template<auto N> struct Test
{
    Test()
    {
        cout << typeid(N).name() << endl;
    }
};

int x = 0;

int main()
{
    int n = 10;

    // Non-type 파라미터    
    Test1<10> t1; // OK
    //st1<n> t2; // Error(변수 안됨);
    
    Test2<red> t3; // OK(enum)
    
    //Test3<&n> t4; // Error(지역변수 주소 안됨)
    Test3<&x> t5; // OK(전역 변수, Static 변수 주소 가능)

    Test4<&main> t6; // OK(함수 포인터 가능)

    // C++17 auto 지원
    Test<10> a1;
    Test<&x> a2;
    Test<&main> a3;
}

 

템플릿(Template)

#include <iostream>
using namespace std;

template<typename T> class List{};

// 템플릿 파라미터(template parameter)
template<typename T, template<typename> class C> class Stack
{
    //C c; // Error(C는 템플릿)
    C<T> c; // OK(C<T> == list<int>)
};

int main()
{
    //List s1; // Error(list는 타입이 아니고 템플릿)
    List<int> s2; // OK(list<int>는 타입)
    Stack<int, list> s3; // OK
}

 

파라미터 기본값 사용

  • 함수 파라미터와 동일하게 기본값 지원
#include <iostream>
using namespace std;

// 기본값 파라미터(default parameter)
template<typename T = int, int N = 10> class Stack
{

};

int main()
{
    Stack<int, 10> s1;
    Stack<int> s2;
    Stack<> s3; // 그냥 stack은 템플릿이므로 꼭 stack<>을 사용해야함
}
반응형

반응형

종속적인 템플릿 이름은 template 키워드를 함께 사용해야 합니다.

#include <iostream>
#include <vector>
#include <list>
using namespace std;

class Test
{
public:
    template<typename T> static void f() {}
    template<typename T> class Complex {};
};

template<typename T> void foo(T a) // T: Test
{
    // OK
    Test::f<int>();

    // Error(임의 타입 T에서 < 연산자의 정의를 알 수 없음)
    //T::f<int>(); 

    // OK(임의 타입 T로 사용할 경우 ::template 키워드를 사용해야한다.)
    T::template f<int>();

    // OK
    Test::Complex<int> c1; 

    // Error(::Complex가 값인지 타입인지 알수 없음)
    //T::Complex<int> c2; 

    // Error(임의 타입 T에서 < 연산자 정의를 알 수 없음)
    //typename T::Complex<int> c3; 

    // OK(임의 타입 T로 사용할 경우 ::template 키워드를 사용해야한다.)
    typename T::template Complex<int> c4; 
}

int main()
{
    Test t;
    foo<Test>(t);
}
반응형

반응형

클래스 이름::xx 접근 가능한 요소들

  • 값: enum 상수, static 멤버 변수

  • 타입: typedef, using

템플릿 의존적으로 타입의 이름에 접근할때는 typename 키워드를 붙여야한다.

  • T::DWORD -> 컴파일러가 DWORD를 값으로 해석

  • typename T:DWORD -> 컴파일러가 DWORD를 타입으로 해석

템플릿이 아닌 경우에는 typename을 사용할 수 없다.

  • typename T::DWORD* p; // OK

  • typename Test::DWORD* p; // Error

#include <iostream>
using namespace std;

class Test
{
public:
    enum { value1 = 1 };
    static int value2;
    typedef int INT;
    using SHORT = short;
    class innerClass {};
    using DWORD = int;
};
int Test::value2;

template<typename T>
int foo(T t)
{
    // 타입으로 해석
    //typename T::DWORD* p;
    
    // 값으로 해석
    //T::DWORD* p;

    return 0;
}

int main()
{
    Test t;
    foo(t);
}

 

 

반응형

반응형

클래스 템플릿안에 friend 함수를 선언하는 방법

  • friend 함수 선언시에 함수 자체를 템플릿 모양으로 선언

  • friend 관계: N:N

#include <iostream>
using namespace std;

template<typename T>
class Point
{
    T x, y;
public:
    Point(T a = { 0 }, T b = { 0 }) : x(a), y(b) {}
    template<typename U>
    friend ostream& operator<<(ostream& os, const Point<U>& p);
};

template<typename T>
ostream& operator<<(ostream& os, const Point<T>& p)
{
    return os << p.x << ", " << p.y;
}


int main()
{
    Point<int> p(1, 2);
    cout << p << endl;

    Point<double> p2(1.2, 2.3);
    cout << p2 << endl;
}

 

  • friend 함수를 일반 함수로 구현하고 구현부를 클래스 템플릿 내부에 포함

  • friend 관계: 1:1

#include <iostream>
using namespace std;

template<typename T>
class Point
{
    T x, y;
public:
    Point(T a = { 0 }, T b = { 0 }) : x(a), y(b) {}
    friend ostream& operator<<(ostream& os, const Point<T>& p)
    {
        return os << p.x << ", " << p.y;
    }
};


int main()
{
    Point<int> p(1, 2);
    cout << p << endl;

    Point<double> p2(1.2, 2.3);
    cout << p2 << endl;
}
반응형

반응형

배열의 특징

  • 자신과 동일한 타입으로 초기화 될 수 없다.
  • 배열의 이름은 배열의 첫번째 요소의 주소로 암시적 형 변환 된다.
  • 배열을 가리키는 참조를 만들 수 있다.

함수 템플릿을 만들때

  • 배열을 값으로 받으면 T는 요소 타입의 포인터로 결정된다.
  • 배열을 참조로 받으면 T는 배열 타입으로 결정된다.
#include <iostream>

template<typename T>
void foo(T a)
{

}

template<typename T>
void goo(T& a)
{

}

int main()
{
    int x[3] = { 1,2,3 };

    foo(x); 
    goo(x);
}

 

관련 예제

  • 문자열의 타입 : char 배열
  • 문자열을 값으로 받으면 T는 const char* 결정되고, 참조로 받으면  const char[]로 결정된다.
  • 크기가 다른 배열은 다른 타입이다.
#include <iostream>

template<typename T>
void foo(T a, T b)
{

}

template<typename T>
void goo(T& a, T& b)
{

}

int main()
{
    foo("orange", "apple"); // ok
    goo("orange", "apple"); // error
}

 

반응형

반응형

Template Argument Type Deduction

  • 컴파일러가 함수 인자를 보고 템플릿의 타입을 결정하는 것을 말한다.
  • 함수 인자의 타입과 완전히 동일한 타입으로 결정되지는 않는다.
#include <iostream>
using namespace std;

// 함수 템플릿 인자가 값 타입(T a) 일때
template<typename T> void foo(T a)
{
    ++a;
}

int main()
{
    int n = 0;
    int& r = n;
    const int c = n;
    const int& cr = c;

    foo(n);  // T : int
    foo(c);  // T : const int ?
    foo(r);  // T : int& ?
    foo(cr); // T : const int& ?
}

Template Argument Type Deduction 원리 1

  • 템플릿 인자가 값 타입일때 (T a)
    • 함수 인자가 가진 const, volatile, reference 속성을 제거하고 T의 타입을 결정한다.
    • 주의 - 인자가 가진 const 속성만 제거 된다.
#include <iostream>
using namespace std;

// 함수 템플릿 인자가 값 타입(T a) 일때
template<typename T> void foo(T a)
{
}

int main()
{
    int n = 0;
    int& r = n;
    const int c = n;
    const int& cr = c;

    foo(n);  // T : int
    foo(c);  // T : int
    foo(r);  // T : int
    foo(cr); // T : int
    
    const char* s1 = "hello";
    foo(s1); // T : char const* // s1이 아닌 char*가 const 이므로 제거 되지 않음
    
    const char* const s2 = "hello";
    foo(s2); // T : char const*  // s2가 const 이므로 인자에 대한 const만 제거됨
}

Template Argument Type Deduction 원리 2

  • 템플릿 인자가 참조 타입일때 (T& a)
    • 함수 인자가 가진 reference 속성을 제거하고 T의 타입을 결정한다.
    • const, volatile 속성은 유지한다.
#include <iostream>
using namespace std;

// 함수 템플릿 인자가 참조 타입(T& a) 일때
template<typename T> void foo(T& a)
{
    ++a;
}


int main()
{
    int n = 0;
    int& r = n;
    const int c = n;
    const int& cr = c;

    foo(n);  // T : int
    foo(c);  // T : const int
    foo(r);  // T : int
    foo(cr); // T : const int
}

 

Template Argument Type Deduction 정리

  • 템플릿 인자가 값 타입(T a)
    • 함수 인자가 가진 const, volatile, reference 속성 제거 후 T 타입 결정
    • 인자의 const 속성만 제거
  • 템플릿 인자가 참조 타입(T& a)
    • 함수 인자가 가진 reference 속성만 제거 후 T 타입 결정
    • const, volatile 속성 유지
    • 인자가 (const T& a)경우 const를 제거하고 T 타입 결정
  • 템플릿 인자가 forwarding 레퍼런스 타입(T&& a)
    • lvalue, rvalue 모두 전달 받음
  • 템플릿 인자가 배열
    • argument decay 발생
반응형

반응형
  • Lazy Instantiation
    • 사용되지 않은 템플릿 클래스는 인스턴스화 되지 않음
    • 사용되지 않은 템플릿 클래스의 멤버 함수도 인스턴스화 되지 않음
    • 사용되지 않은 템플릿 클래스의 static 함수도 인스턴스화 되지 않음
    • 사용되지 않은 전역 템플릿 변수도 인스턴스화 되지 않음
template<typename T> class A
{
    T data;
public:
    void foo(T n) { *n = 10; } // 참조 오류 코드
};

int main()
{
    A<int> a;
    // a.foo(1); 템플릿의 멤버 함수가 호출되지 않는다면 인스턴스화 되지 않으므로 컴파일 성공
}
struct Resource1
{
    Resource1() { cout << "Resource1()" << endl; }
    ~Resource1() { cout << "~Resource1()" << endl; }
};

struct Resource2
{
    Resource2() { cout << "Resource2()" << endl; }
    ~Resource2() { cout << "Resource2()" << endl; }
};

template<typename T> struct Test
{
    Resource1 res1; // 클래스내 메모리 할당이 필요하므로 인스턴스화됨
    static Resource2 res2; // static 멤버는 별도 메모리 할당으로 인스턴스화 되지 않음
};
template<typename T> Resource2 Test<T>::res2; // 사용되지 않아서 인스턴스화 되지 않음

int main()
{
    cout << "main" << endl; // 1. 메인 출력
    Test<int> t; // 2. Resource1 생성자 출력
}

 

  • if 문과 Lazy Instantiation
    • if 문은 "실행시간 조건문"으로 컴파일 시간에 조건이 false로 결정되어도 if 문에 있는 코드는 항상 사용되는 것으로 간주함
    • C++17 if constexpr는 "컴파일 시간 조건문"으로 조건이 false로 결정되면 if 문에 포함된 코드는 사용되지 않는 것으로 간주됨
    • 동일한 이름의 함수가 여러개 있을 때 어떤 함수를 호출할 지 결정하는 것은 컴파일 시간에 결정되므로 선택되지 않은 함수가 템플릿이라면 인스턴스화 되지 않음
template<typename T> void foo(T n)
{
    *n = 10; // 참조 오류 코드
}

int main()
{
    if(false) // 런타임 조건식이므로 foo는 인스턴스화 됨
        foo(0);

    if constexpr ( false ) // C++17의 컴파일타임 조건식이므로 foo는 인스턴스화 되지 않음
        foo(0);
}
반응형

+ Recent posts