leesangwon0114

I am Research Engineer. Currently working in KT.

C++Template 5. template type deduction

08 Aug 2018 » c++

codenuri 강석민 강사 강의 내용기반으로 정리한 내용입니다.

boost::type_index

  1. C++ 표준의 typeid() 연산자 사용
    • const, volatile, reference 를 조사할 수 없음
  2. boost의 type_index 라이브러리 사용
    • const, volatile, reference 를 조사 할 수 있음
#include <iostream>
using namespace std;

template<typename T> void foo(const T a) 
{
    cout << "T : " << typeid(T).name() << endl;
    cout << "a : " << typeid(a).name() << endl;
}

int main()
{
    // foo<int>(3);

    foo(3); // T: int, a: const int
    foo(3.3);   // T: double, a: const double
}

출력결과는 모두 int, double 임 -> boost 사용

  1. <boost\type_index.hpp>
  2. namespace boost::typeindex 안에 있음
  3. type_id_with_cvr().pretty_name() 으로 사용
  4. 변수의 타입을 조사 할 때는 decltype()을 사용함
    • type_id_with_cvr<decltype(a)>().pretty_name()
#include <iostream>
#include <boost\type_index.hpp>
using namespace std;
using namespace boost::typeindex;

template<typename T> void foo(const T a) 
{
    cout << type_id_with_cvr<T>().pretty_name() << endl;
    cout << type_id_with_cvr<decltype(A)>().pretty_name() << endl;
}

int main()
{
    foo(3); // T: int, a: const int
    foo(3.3);   // T: double, a: const double
}

출력결과

Alt text


Template type deduction

Template Argument Type Deduction

  1. 컴파일러가 함수 인자를 보고 템플릿의 타입을 결정하는 것
  2. 함수 인자의 타입과 완전히 동일한 타입으로 결정되지 않는다.

아래 모두 T는 int 로 결정됨


template<typename T> void foo(T a) 
{
    ++a;
}

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

    foo(n); // T: int
    foo(c); // T: const int ? int
    foo(r); // T: int& ? int    (원래 원본은 바뀌지 않는 것을 기대)
}

  1. 규칙1. 템플릿 인자가 값 타입일 때(T a)
    • 함수 인자가 가진 const, volatile, reference 속성을제거하고 T의 타입을 결정
    • 주의! - 인자가 가진 const 속성만 제거 된다.

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

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";   // s1을 따라가면 const
    foo(s1);    // T: char const*

    const char* const s2 = "hello";
    foo(S2);    // T: char const*
}

  1. 규칙2. 템플릿 인자가 참조 타입일 때(T& a)
    • 함수 인자가 가진 reference 속성만 제거하고 T의 타입을 결정
    • const 와 volatile 속성은 유지한다. 단, 템플릿 인자가 const T& 일 경우는 함수 인자가 가진 const를 제거하고 T 타입을 결정

// 함수 템플릿 인자가 참조 타입(T& a) 일 때.
template<typename T> void foo(T& a) 
{
    cout << type_id_with_cvr<T>().pretty_name() << endl;
    cout << type_id_With_cvr<decltype(a)>().pretty_name() << endl;
}

// 함수 템플릿 인자가 const 참조 타입(const T& a) 일 때.
template<typename T> void goo(const T& a) 
{
    cout << type_id_with_cvr<T>().pretty_name() << endl;
    cout << type_id_With_cvr<decltype(a)>().pretty_name() << endl;
}


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

    foo(n); // T: int,  a: int&
    foo(c); // T: const int,  a: const int& (const를 일반참조로 가르킬 수 없음->const 유지시킴)
    foo(r); // T: int,  a: int&
    foo(cr); // T: const int, a: const int&

    
    goo(n); // T: int,  a: const int&
    goo(c); // T: int,  a: const int&
    goo(r); // T: int,  a: const int&
    goo(cr); // T: int, a: const int&
}

정리


template<typename T> void foo(T a)  {}
template<typename T> void foo(T& a)  {}
template<typename T> void foo(const T& a)  {}
template<typename T> void foo(T&& a)  {}

int n = 10;
const int& cr = n;
foo(cr);

int x[3] = {1,2,3};
foo(x); // 배열을 함수 템플릿에 전달할 때 주의점

1.규칙1

2.규칙2

3.규칙3. 템플릿 인자가 forwarding 레퍼런스일때(T&&)

  • lvalue와 rvalue를 모두 전달 받는다.

4.규칙4. 배열을 전달받을때 - argument decay 발생


array name


int main()
{
    int n;  // 변수 이름: n, 타입: int
    int *p1 = &n;   

    double d;   // 변수 이름: d, 타입: double
    double *p2 = &d;

    int x[3] = {1,2,3}; // 변수이름: x, 타입: int[3] (이름을 빼면 타입만 남음)

    // 배열 x의 주소..
    int (*p3)[3] = &x;    // *과 [] 연산자 2개가 있어 우선순위 높이기 위해 괄호를 붙임

    int *p4 = x; // 배열의 주소 아님..
}


int main()
{
    int n1 = 10; 
    int n2 = n1; // 모든 변수는 자신과 동일한 타입의 변수로 초기화(복사) 될 수 있다.

    double d1 = 3.4;
    double d2 = d1;

    int x1[3] = {1,2,3}; // x1의 타입 : int[3]
    int x2[3] = x1; // error. 배열은 크기가 커 자산과 동일한 타입의 배열로 복사가 안됨...

    int *p1 = x1; // 대신 배열의 이름은 첫번째 요소의 주소로 암시적 형변환 된다.

}


int main()
{
    int x1[3] = {1,2,3}; 
    
    int(*p1)[3] = &x; // 배열의 주소
    int *p2 = x; // 배열의 이름이 배열의 첫번째 요소의 주소로 암시적 형변환 된 표현

    printf("%p, %p\n", p1, p1+1);   //12
    printf("%p, %p\n", p2, p2+1);   //4
}

Alt text



int main()
{
    int x1[3] = {1,2,3}; 
    
    int(*p1)[3] = &x; // 배열의 주소
    int *p2 = x; // 배열의 이름이 배열의 첫번째 요소의 주소로 암시적 형변환 된 표현

    printf("%p, %p\n", p1, p1+1);   //12
    printf("%p, %p\n", p2, p2+1);   //4

    // p1: 배열의 주소, *p1: 배열
    (*p1)[0] = 10;

    // p2: 요소의 주소, (int*)
    *p2 = 10;
}

배열의 이름이 배열의 첫번째 요소의 주소로 암시적 형변환 된다는 원리 이해하기!


argument decay

  1. 배열의 특징
    • 자신과 동일한 타입으로 초기화 될 수 없다.
    • 배열의 이름은 배열의 1번째 요소의 주소로 암시적 형 변환 된다.
    • 배열을 가리키는 참조를 만들 수 있다.
  2. 함수 템플릿을 만들 때
    • 배열을 값으로 받으면 T는 요소 타입의 포인터로 결정된다.
    • 배열을 참조로 받으면 T는 배열 타입으로 결정된다.

template<typename T>
// void foo(T a) // void foo(int a[3])
void foo(T a) // void foo(int* a) -> argumnet Decay(배열을 포인터로 받음)
{
    cout << type_id_with_cvr<T>().pretty_name() << endl;
    cout << type_id_with_cvr<decltype(a)>().pretty_name() << endl;
}
template<typename T>
void goo(T& a)  // void goo(int (&a)[3])
{
    cout << type_id_with_cvr<T>().pretty_name() << endl;
    cout << type_id_with_cvr<decltype(a)>().pretty_name() << endl;
}
int main()
{
    int x1[3] = {1,2,3}; 

    foo(x); // T: int[3] error
    goo(x); // T: int[3] ok
    
    int y[3] = x;   // error
    int *p = x;     // ok
    int (&r)[3] = x;    // ok

}

출력결과

Alt text

  1. 문자열의 타입: char 배열

  2. 문자열을 값으로 받으면 T는 const char* 결정되고, 참조로 받으면 const char[]로 결정된다.

  3. 크기가 다른 배열은 다른 타입임.


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

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

int main()
{
    // foo("orange", "apple");
    // goo("orange", "apple"); // error (크기가 다르므로 다른 타입)
    goo("orange", "aapple");
}