반응형

type traits 개념

  • 컴파일 타임에 타입에 대한 정보를 얻거나 변형된 타입을 얻을때 사용하는 도구(메타 함수)

  • <type_traits> 헤더로 제공됨(c++11)

type query를 위한 type traits 만드는 방법

  • 메인 템플릿(Primary template)에서 false 반환( value = false )

  • 부분 특수화(Partial specialization)에서 true 반환( value = true )

 

is_pointer 예제

  • 간단히 템플릿 파라미터 T가 포인터 타입 여부 확인 도구

  • 메인 템플릿(Primary template)에서 false 반환( value = false )
  • 포인터 타입용 부분 특수화(Partial specialization)에서 true 반환( value = true )
#include <iostream>
#include <type_traits>
using namespace std;

template<typename T> struct is_pointer_custom
{
    enum { value = false }; 
};

// 포인터 타입에 대해서 부분 특수화 필요
template<typename T> struct is_pointer_custom<T*>
{
    enum { value = true };
};

template<typename T> void foo(T v)
{
    if (is_pointer_custom<T>::value)
        cout << "pointer" << endl;
    else
        cout << "not pointer" << endl;
}

int main()
{
    int n = 3;
    foo(n);
    foo(&n);
}

 

is_pointer 개선 예제

  • c++11 기준 코드 개선
  • 좀더 다양한 포인터 타입 지원(const, volatile, const volatile)
#include <iostream>
#include <type_traits>
using namespace std;

template<typename T> struct is_pointer_custom
{
    //enum { value = false }; 
    static constexpr bool value = false; // c++11
};

// 포인터 타입에 대해서 부분 특수화 필요
template<typename T> struct is_pointer_custom<T*>
{
    //enum { value = true };
    static constexpr bool value = true; // c++
};

// 포인터 타입에 대해서 부분 특수화 필요
template<typename T> struct is_pointer_custom<T* const>
{
    //enum { value = true };
    static constexpr bool value = true; // c++
};

// 포인터 타입에 대해서 부분 특수화 필요
template<typename T> struct is_pointer_custom<T* volatile>
{
    //enum { value = true };
    static constexpr bool value = true; // c++
};

// 포인터 타입에 대해서 부분 특수화 필요
template<typename T> struct is_pointer_custom<T* const volatile>
{
    //enum { value = true };
    static constexpr bool value = true; // c++
};

int main()
{
    cout << is_pointer_custom<int>::value << endl;
    cout << is_pointer_custom<int*>::value << endl;
    cout << is_pointer_custom<int* const>::value << endl;
    cout << is_pointer_custom<int* volatile>::value << endl;
    cout << is_pointer_custom<int* const volatile>::value << endl;
    cout << is_pointer_custom<int* volatile const>::value << endl;
}
반응형

반응형

constexpr 함수

  • 함수앞에 constexpr 붙이면 파라미터가 컴파일 타임 상수 일 경우 함수를 컴파일 시간에 연산(성능 이점)

  • 일반 변수 파라미터 입력시 일반 함수처럼 동작

#include <iostream>
#include <type_traits>
using namespace std;

template<int N> struct Check
{

};

// constexpr 함수(c++11)
constexpr int add(int a, int b)
{
    return a + b;
}

int main()
{
    int n1 = 1, n2 = 2;

    int n = add(n1, n2);   // OK(일반 변수 파라미터 입력 시 일반 함수처럼 동작)
    int m = add(1, 2);     // OK(컴파일 타임 상수 파라미터 입력 시 컴파일 타임에 연산)
    Check<add(1, 2)> c;    // OK(컴파일 타임에 연산됨으로 템플릿 파라미터로 사용 가능)
    Check<add(n1, n2)> c2; // Error(일반 함수처럼 동작하므로 템플릿 파라미터로 사용 불가)
}
반응형

반응형

템플릿 메타 프로그래밍

  • 컴파일 시간에 연산을 수행하는 개념
  • 템플릿 파라미터 5를 받았을때 5 * 4 * 3 * 2 * 1 값을 반환하는 Factorial 구현
#include <iostream>
#include <type_traits>
using namespace std;

//  템플릿 메타 프로그래밍(template meta programming)
template<int N> struct Factorial
{
    enum { value = N * Factorial<N-1>::value };
};

// 재귀의 종료를 위해 특수화(Specialization)
template<> struct Factorial<1>
{
    enum { value = 1 };
};

int main()
{

    int n = Factorial<5>::value; // 5 * 4 * 3 * 2 * 1 => 120
    // 5 * Factorial<4>::value
    // 4 * Factorial<3>::value
    // 3 * Factorial<2>::value
    // 2 * Factorial<1>::value
    // 1

    cout << n << endl;
}

 

C++11 이후 부터는 enum대신 constexpr을 사용할 수 있다.

#include <iostream>
#include <type_traits>
using namespace std;

//  템플릿 메타 프로그래밍(template meta programming)
template<int N> struct Factorial
{
    //enum { value = N * Factorial<N-1>::value };
    static constexpr int value = N * Factorial<N - 1>::value;
};

// 재귀의 종료를 위해 특수화(Specialization)
template<> struct Factorial<1>
{
    //enum { value = 1 };
    static constexpr int value = 1;
};

int main()
{

    int n = Factorial<5>::value; // 5 * 4 * 3 * 2 * 1 => 120
    // 5 * Factorial<4>::value
    // 4 * Factorial<3>::value
    // 3 * Factorial<2>::value
    // 2 * Factorial<1>::value
    // 1

    cout << n << endl;
}
반응형

반응형

XTuple(Couple 선형화 구현)

  • Couple 재귀 호출 대신 선형 호출 할 수 있는 패턴 지원
    • Couple<int, Couple<int, double>...> -> XTuple<int, int, double...> 형태로 개선
  • Null 클래스 활용(Empty class)

    • 멤버 없는 클래스

    • 크기는 항상 1(sizeof(Null))

    • 멤버는 없지만 타입이므로 함수 오버로딩이나 템플릿 인자로 활용

  • 상속 활용 기술

  • 개수의 제한을 없앨 수 없을까? C+++ Variadic template

#include <iostream>
#include <type_traits>
using namespace std;

template<typename T, typename U> struct Couple
{
    T v1;
    U v2;

    enum { N = 2 };
};

// 빈 파라미터로 활용하기 위한 empty class
struct Null {};

// 2개이상 5개 미만의 타입전달
template<typename P1,
typename P2,
typename P3 = Null,
typename P4 = Null,
typename P5 = Null> 
class XTuple 
    : public Couple<P1, XTuple<P2, P3, P4, P5, Null>>
{

};

// XTuple 상속을 종료하기 위한 특수화
template<typename P1, typename P2>
class XTuple<P1, P2, Null, Null, Null>
    : public Couple<P1, P2>
{

};

int main()
{
    // Couple을 선형화하여 XTuple 형태로 사용
    XTuple<int, char, long, short, double> t5;
}
반응형

반응형

Couple 

  • 2개의 인자로 받은 데이터를 보관하는 컨테이너

  • 인자로 Couple 타입도 전달 가능

  • 부분 특수화(Partial specialization)를 할때 파라미터 수를 잘 선택해야함

  • 파라미터로 자기 자신을 재귀로 입력 시 N 표현 방법 고려

  • 1번째 인자, 2번째 인자, 모든 인자가 Couple 일 경우 특수화 방법 고려

#include <iostream>
#include <type_traits>
using namespace std;

// 보관 데이터 수를 출력하는 함수
template<typename T> void printN(const T& cp)
{
    cout << T::N << endl;
}

// 2개의 인자를 보관하는 데이터(메인 템플릿)
template<typename T, typename U> struct Couple
{
    T v1;
    U v2;

    enum { N = 2 };
};

// 2번째 인자가 Couple일 경우
template<typename A, typename B, typename C>
struct Couple<A, Couple<B, C>>
{
    A v1;
    Couple<B, C> v2;
    enum { N = Couple<B, C>::N + 1 };
};

// 1번째 인자가 Couple일 경우
template<typename A, typename B, typename C>
struct Couple<Couple<A, B>, C>
{
    Couple<A, B> v1;
    C v2;
    enum { N = Couple<A, B>::N + 1 };
};

// 1번째, 2번째 모든 인자가 Couple일 경우
template<typename A, typename B, typename C, typename D>
struct Couple<Couple<A, B>, Couple<C, D>>
{
    Couple<A, B> v1;
    Couple<C, D> v2; 
    enum { N = Couple<A, B>::N + Couple<C, D>::N };
};

int main()
{
    // 2개의 인자 데이터
    Couple<int, double> c2;
    
    // 2번째 인자에 Couple 데이터를 입력 할 경우(총 3개 보관)
    // 가변 영역의 데이터는 int, int, char 총 3개
    Couple<int, Couple<int, char>> c3;
    
    // 2번째 인자의 Couple 데이터의 2번째 인자에 다시 Couple 데이터를 입력 할 경우(총 4개 보관)
    // Couple 데이터를 중첩하여 사용하였으나 가변 영역은 동일하게 int, int, T로 총 3개
    Couple<int, Couple<int, Couple<int, char>>> c4;
    
    // 1번째, 2번째 인자 모두 Couple 데이터를 입력 할 경우(총 4개 보관)
    Couple<Couple<int, int>, Couple<int, int>> c5;

    // 보관 데이터 수 출력
    printN(c2);
    printN(c3);
    printN(c4);
    printN(c5);
}

 

반응형

반응형

IfThenElse 예제

  • 컴파일 시간 bool값에 따라 type을 선택하는 도구
  • 일반적으로 IfThenElse, IF, Select 등 이름으로 구현함
  • C++ 표준에는 conditional 이름으로 제공<type_traits> 헤더
#include <iostream>
using namespace std;

// IfThenElse 메인 템플릿
template<bool b, typename T, typename F> struct IfThenElse
{
    typedef T type;
};

// 부분 특수화
template<typename T, typename F> 
struct IfThenElse<false, T, F>
{
    typedef F type;
};

int main()
{
    // 첫번째 파라미터를 true로 할 경우 T 반환
    IfThenElse<true, int, double>::type t0; // int
    
    // 첫번째 파라미터를 false로 셋팅할 경우 F 반환
    IfThenElse<false, int, double>::type t1; // double

    cout << typeid(t0).name() << endl;
    cout << typeid(t1).name() << endl;
}

 

conditional 활용

#include <iostream>
#include <type_traits>
using namespace std;

template<size_t N> struct Bit
{
    // bit 단위로 데이터 관리가 필요할 경우 조건별 타입을 사용할 수 있음
    // 8이하 : char, 16이하 : short, 32이하 : int...
    using type = typename conditional<(N <= 8), char, 
        typename conditional<(N <= 16), short, int>::type>::type;

    type bitmap;
};

int main()
{
    Bit<32> b1;
    Bit<8> b2;
    Bit<16> b3;

    cout << sizeof(b1) << endl;
    cout << sizeof(b2) << endl;
    cout << sizeof(b3) << endl;
}

 

반응형

반응형

템플릿 특수화(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<>을 사용해야함
}
반응형

+ Recent posts