«   2024/03   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Archives
Today
Total
03-28 21:02
관리 메뉴

lancelot.com

About C++ template 본문

프로그래밍

About C++ template

lancelot50 2022. 5. 28. 11:24
  • template instantiation 결과로 생성된 코드를 확인하고 싶을때
    • compiler explorer site 에서 어셈블리의 생성을 확인 ( http://godbolt.org)
    • https://cppinsights.io/ 에서 코드 생성을 확인 ( template 뿐만아니라 range for 등의 코드 생성도 볼 수 있음)
    • 인스턴스화 된 함수 이름 출력 ( 비표준 MS 용 매크로 __FUNCSIG__,  g++ 에서는 __PRETTYO_FUNCTION__ )
    • C++20 의 <source_location>  -> 하지만 MSVC에서는 __FUNCTION__ 처럼 동작해서 의미가 없음

 

  • trailing return type

- template 에서 return type을 타입추론을 통해서 생성해야할 경우가 있는데, 함수 선언에서

decltype<a+b> Max(T a, T b)

처럼, 리턴타입 앞에 타입 추론 부분이 있으면 a, b 가 선언하기도전에 사용된거라서 오류가 발생.

그래서 

auto max(T a, T b)  -> decltype(a+b)

라고 뒤에다가 쓴다... 라고 하는데(C++11 에서 도입. 14부터는 앞에 쓸 수 있게 됨).. 좀 그렇다. 저걸 위해서 뒤에다가 너저분하게 쓰게 만들다니.....  진작에 앞에 쓸 수 있게 해주지 왜.... -_-;

https://sillim83.tistory.com/entry/CPP-%EC%B4%88%EA%B8%89-%EA%B0%95%EC%A2%8C-15-%ED%9B%84%EC%9C%84-%EB%B0%98%ED%99%98-%ED%83%80%EC%9E%85-trailing-return-type

 

CPP 초급 강좌 15. C++ 후위 반환 타입 trailing return type

CPP 초급 강좌 15. C++ 후위 반환 타입 trailing return type c++11 부터 후위반환 타입 이라는 표현법이 생겼습니다. // int f1(int a) // auto f1(int a) -> int auto f1(int a) -> int { return a + a; } int m..

sillim83.tistory.com

 

  • template parameter 로 들어갈 수 있는 것들의 종류 3가지 - type, tempate, non-type

그중에 non-type의 종류 - NTTP (Non-Type Template Parameter)

정수형 상수 컴파일시간 상수만 가능, 변수안됨
실수형 상수 C++20 부터 지원
enum 상수 enum 또는 enum class
포인터, 함수포인터 static storage 만 가능. 지역변수 주소 안됨
auto C++ 17부터 가능

 

  • template parameter의 auto(C++17) 와 function template의 auto(C++20) 를 구분할 것
// template parameter의 auto ( C++ 17 )
template<int N, double D, auto A> // auto는 int, double, pointer 등 모든 non-type parameter 전달 가능
struct Triple
{
};


// function template 의 auto ( C++ 20 )
void foo(auto a)
{
}
// 위의 것은 아래와 같다
template<typename T>
void foo(T a)
{
}
  • array
#include<iostream>
#include<vector>
template<typename T, int N>
struct array
{
	T arr[N] = {};
public :
	std::size_t size() const { return N; }
	T& operator[](int idx) { return arr[idx]; }
	const T& operator[](int idx) const { return arr[idx]; }

	using value_type=T;
	using iterator = T*;

	auto begin() { return arr; }
	auto end() { return arr + N; }
};

int main()
{
	int x[5] = { 1,2,3,4,5 };
	std::vector<int> v = { 1,2,3,4,5 };
	array<int, 5> a = {1,2,3,4,5};

	std::cout << a.size() << std::endl;
	a[0] = 10;

	for (auto e : a)
		std::cout << e<< std::endl;
}

 

  • Template type deduction rule ( auto 의 타입이 결정되는 rule 도 같다)
// template type deduction rule
// T : 함수 인자가 가진 "const, volatile, reference 속성을 제거"하고 T타입을 결정
// T& : 함수 인자가 가진 "reference 속성만 제거"하고 T 타입을 결정. const, volatile 은 유지
// T&& : forwarding reference -> lvalue 와 rvalue를 모두 받을 수 있는 템플릿
//               T     T&&
// 3(rvalue)   int    int&&
// n(lvalue)   int&   int&

#include<iostream>
template<typename T>
void f3(T&& arg)
{
	std::cout << __FUNCSIG__ << std::endl;
}

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

	f3(3);
	f3(n);
	f3(c);
	f3(r);
	f3(cr);
}

 

  • arguement decay
#include<iostream>
template<typename T>
void f1(T arg)
{
	std::cout << __FUNCSIG__ << std::endl;
}

template<typename T>
void f2(T& arg)
{
	std::cout << __FUNCSIG__ << std::endl;
}

int main()
{
	int x[3] = { 1,2,3, };
	f1(x);// T=int* , arg=int*
	f2(x);// T=int[3], arg=int(&)[3]

	// int x[3] 일때 -> x의 정확한 타입은 int[3]
	// auto a1=x;		int a1[3]=x;  -> compile error
	//					int* a1=x;	  -> compile ok
	auto a1 = x; // auto = int* , int* a1=x;
	auto& a2 = x;// auto = int[3]
	             // a2   = int(&)[3]

	// argument decay : 배열 전달시 포인터로 받게되는 현상
	// f1(T arg)		T=int*
	//					f1(int* arg)
	// f2(T&arg)		T=int[3], arg=int(&)[3]
	//					f2(int(&arg)[3]
}
#include<iostream>
template<typename T>
void f1(T s1, T s2)
{
	std::cout << __FUNCSIG__ << std::endl;
}

template<typename T>
void f2(T& s1, T& s2)
{
	std::cout << __FUNCSIG__ << std::endl;
}

int main()
{
	// 문자열의 정확한 타입 -> char 배열 ( char* 가 아님)
	// "banana" -> char[7], "apple" -> char[6]
	f1("banana", "apple"); // argument decay 때문에 const char* 로 변경되어서  compile ok
	f2("banana", "apple"); // T 타입 1개를 받는데 입력은 char[7], char[6] 2가지라서 compile error
}

 

  • explicit instantiation - template 은 선언과 정의를 같은 파일에 두어야하는데, 정의를 다른 파일에 두고 explicit instantiation 을 하면 compile 이 가능하다.
template<typename T>
void fn(T a)
{
}

template void fn<int>(int);// funciton template 의 explicit instantiation
template void fn<>(double);
template void fn(char);


template<class T>
class Type
{
    void mf1(){}
    void mf2(){}
};

template class Type<int>;  			// class template 의 explicit instantiation
template void Type<double>::mf1(); 	// class member function 의 explicit instantiation

int main()
{
//    fn(3);
}

 

  • specialization
#include<iostream>

template<typename T>					// primary template
class Vector
{
	T* ptr;
	std::size_t size;
public :
	Vector(std::size_t sz) : size(sz)
	{
		ptr = new T[sz];
	}
	~Vector() { delete[] ptr; }
};

template<>								// template specialization
class Vector<bool>
{
	int* ptr;
	std::size_t size;
public:
	Vector(std::size_t sz) : size(sz)
	{
		ptr = new int[sz/32+1];
	}
	~Vector() { delete[] ptr; }
};

template<typename T>					// partial specialization
class Vector<T*>
{
	T* ptr;
	std::size_t size;
public:
	Vector(std::size_t sz) : size(sz)
	{
		ptr = new T[sz];
	}
	~Vector() { delete[] ptr; }
};

int main()
{
	Vector<int> v1(5);
	Vector<double> v2(5);
	Vector<bool> v3(5);
}

 

  • primary template, specialization, partial specialization
#include<iostream>
// primary template
template<typename T, typename U> 
struct Object
{
	static void fn() {	std::cout << "T, U" << std::endl;}
};

// specialization
template<> 
struct Object<int, short>
{
	static void fn() { std::cout << "int, short" << std::endl; }
};

// partial specialization
template<typename T, typename U>
struct Object<T*, U>
{
	static void fn() { std::cout << "T*, U" << std::endl; }
};

template<typename T>
struct Object<T, T>
{
	static void fn() { std::cout << "T, T" << std::endl; }
};

template<typename U>
struct Object<int, U>
{
	static void fn() { std::cout << "int, U" << std::endl; }
};

template<typename A, typename B, typename C>
struct Object<A, Object<B, C>>
{
	static void fn() { std::cout << "Object<A, Object<B, C>>" << std::endl; }
};

int main()
{
	Object<char, double>::fn();		// T, U
	Object<int, short>::fn();		// int, short
	Object<short*, double>::fn();	// T*, U
	Object<float, float>::fn();		// T, T
	Object<int, float>::fn();		// int, U

	Object<long, Object<char, short>>::fn();	// A, Object<B, C>
}

 

  • template partial specialization 시의 default parameter
#include<iostream>
template<typename T1, typename T2=T1>
struct Object
{
	static void print()
	{
		std::cout << typeid(T1).name() << ", " << typeid(T2).name() << std::endl;
	}
};
// partial specialization 에는 default parameter 를 표기하지 않고, primary template 의 것을 가져와서 사용함
template<typename T1, typename T2>
struct Object<T1*, T2>
{
	static void print()
	{
		std::cout << typeid(T1).name() << ", " << typeid(T2).name() << std::endl;
	}
};

int main()
{
	Object<int*>::print();	// 이경우 int, int* 로 출력
}

 

  • specialization 또는 partial specialization 버전만 사용하고 싶을때는 primary template을 선언만 제공한다
// 특수화 또는 부분 특수화 버전만 사용하고, primary template 은 사용하지 않기를 원할때는
// primary template을 선언만 제공한다.
template<typename T, int N>
struct Object;

template<typename T>
struct Object<T, 1>
{
	static void print() { std::cout << typeid(T).name() << ", " << std::endl; }
};
template<typename T>
struct Object<T, 2>
{
	static void print() { std::cout << typeid(T).name() << ", " << std::endl; }
};

int main()
{
	Object<int, 1>::print();
	Object<int, 2>::print();
	Object<int, 3>::print();// error 
}

 

  • variable template specialization
#include<iostream>
// variable template 의 specialization 을 이용하면 편리한 경우가 있다.
// ex> class의 버전 제공
template<typename>
inline constexpr int version = -1;

class AAA;
template<>
inline constexpr int version<AAA> = 2020;

class BBB;
template<>
inline constexpr int version<BBB> = 2021;

int main()
{
	std::cout << version<AAA> << std::endl;
	std::cout << version<BBB> << std::endl;
	std::cout << version<int> << std::endl;	// version 정보를 제공하지 않을 경우 default -1
}

 

  • borrowed range
    • C++ 20 에 소개된 용어
    • 자원을 소유하지 않고 다른 range(container)가 소유한 자원을 사용하는 range
    • std::ranges::enable_borrowed_range 라는 variable template으로 조사가능
    • <ranges>
    • variable template specialization 으로 구현
#include<iostream>

// C++ 20 의 enable_borrowed_range, <ranges>
template<typename>
inline constexpr bool enable_borrowed_range = false;

template<typename T, typename Traits>
inline constexpr bool enable_borrowed_range<std::basic_string_view<T, Traits> > = true;

int main()
{
	std::string s1 = "to be or not to be";
	std::string s2 = s1;

	std::string_view sv = s1;

	std::cout << enable_borrowed_range<std::string> << std::endl;		// 0
	std::cout << enable_borrowed_range<std::string_view> << std::endl;	// 1
}

 

  • integral constant
    1. int2type - 0과 1은 같은 type이라서 function overloading을 이용할 수 없지만, int2type<> 템플릿을 사용해서 템플릿에 넣으면 다른 타입으로 만들어서 함수 오버로딩을 이용할 수 있게됨.
    2. C++11 에서 integral constant로 발전
#include<iostream>
template<int N>
struct int2type{ enum { value = N  }; };

void fn(int n) { std::cout << __FUNCSIG__ << std::endl; }

void fn(int2type<0>) {	std::cout << __FUNCSIG__ << std::endl; }
void fn(int2type<1>) {	std::cout << __FUNCSIG__ << std::endl; }

int main()
{
	fn(0);
	fn(1);

	int2type<0> t1;
	int2type<1> t2;

	fn(t1);
	fn(t2);
}
  • 이것을 이용하면, 타입에 따라 빌드되지 않는 템플릿 코드를 만들 수 있게됨.
#include<iostream>

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

template<typename T>
struct is_pointer<T*> { enum { value = true }; };

template<int N>
struct int2type { enum { value=N }; };

template<typename T>
void printv_imp(const T& value, int2type<1>)
{
	std::cout << value << " : " << *value << std::endl;
}
template<typename T>
void printv_imp(const T& value, int2type<0>)
{
	std::cout << value << std::endl;
}

template<typename T>
void printv(const T& value)
{
	printv_imp(value, int2type<is_pointer<T>::value>());
}

int main()
{
	int n = 10;
	printv(&n);
	printv(n);
}
// int2type
// -> int 타입의 상수를 type으로 만드는 도구
// -> int 뿐만 아니라 다른 정수형 타입의 상수도 타입으로 만들수 있으면 좋지않을까?
// std::integral_constant
// -> C++11 표준
// -> int2type의 발전된 형태

template<int N> struct int2type
{
	enum { value=N };
};

template<typename T, T N>
struct integral_constant
{
	static constexpr T value= N;
};

int main()
{
	int2type<0> t1;
	int2type<1> t2;

	integral_constant<int, 0> n0;
	integral_constant<int, 1> n1;
	integral_constant<short, 1> n2;
}

 

  • type transformation
    • T가 포인터인지 조사하는 방법
      •  C++11  std::is_pointer<T>::value
      •  C++17  std::is_pointer_v<T>
// variable template을 이용
template<typename T>
constexpr bool is_pointer_v=std::is_pointer<T>::value;
  • T에서  포인터를 제거한 타입 구하기
    • C++11 typename std::remove_pointer<T>::value
    • C++14 std::remove_pointer_t<T>
// using template 을 이용
template<typename T>
using remove_pointer_t=std::remove_pointer<T>::type;