C++ Templates

LeemHyungJun·2022년 9월 20일
0

C++ Study

목록 보기
6/12
post-thumbnail

예시코드)
https://github.com/GbLeem/CPP_Study/tree/main/NoCode
43.cpp ~ 47.cpp

Template Introduction

  • Template의 종류
    • Function Template
    • Class Template
    • Alias Template
    • Variable Template
    • Template Concept
  • Function Template 예시
#include<algorithm>
#include<iostream>
#include<vector>

 template<typename T>
T add(T a, T b)
{
	return a + b;
}

int main()
{
	//STL 예시1
	int maxI = std::max<int>(1, 5);
	float maxF = std::max<float>(1.2f, 2.5f);

	std::cout << maxI << std::endl;
	std::cout << maxF << std::endl;

	//STL 예시2
	std::vector<int> vecI;
	std::vector<float> vecF;

	//직접 만든 template 함수 예시
	int resI = add<int>(1, 2);
	auto res = add(1.2, 2.2); //type deduction
}

Template Type Deduction

  • Type Deduction : auto 키워드를 사용
  • Type Deduction 예시
#include<iostream>
int main()
{
	auto a = 1;
	std::cout << typeid(a).name() << std::endl; //int
	auto b = 1.1;
	std::cout << typeid(b).name() << std::endl; //double
	auto c = "ABC";
	std::cout << typeid(c).name() << std::endl; //const char*
}
  • Template Type Deduction : 컴파일러가 템플릿에 타입을 적지 않아도 알아서 지정해주는 것
  • <>을 이용해서 지정해 주는 것이 컴파일러가 자동으로 지정하는 것보다 우선순위가 높다.
    ex) printVar<int>(1.2); 의 경우 int, 1이 출력
  • Template Type Deduction 예시
template<typename T>
void printVar(T a)
{
	std::cout << typeid(a).name() << std::endl;
	std::cout << a << std::endl;
}
int main()
{
	int a = 1;  
	printVar<int>(a); //int, 1출력 
	printVar(a); 	  //int, 1출력 ->  deduction
}
  • R-value를 파라메터로 받는 template
    -> r-value reference가 아닌 forward reference라고 한다.
    -> l-value, r-value reference 둘 다 가능해진다.
  • forward reference 예시
#include<iostream>

template<typename T>
void printVar(T &&a) //forward reference(=universal reference)
{
	std::cout << typeid(a).name() << std::endl;
	std::cout << a << std::endl;
}
int main()
{
	int a = 1;
    //printVar<int>(a); 컴파일 에러!!
    
    printVar(a); //컴파일 에러 발생 X 
    //-> T는 int&, function argument는 int&
    
    printVar(std::move(a)); 
    //-> T는 int, function argument는 int&&
    
    return 0;
}
  • forward reference 예시2
#include<iostream>
#include<string>

template<typename T>
void printVar(T &&a)
{
	std::cout << a << std::endl;
}
int main()
{
	int a = "good";
    printVar(a); 			//good 출력
    printVar(std::move(a)); //good 출력 
    
    return 0;
}
  • forward reference 예시3
#include<iostream>
#include<string>

template<typename T>
void printVar(T &&a)
{
	std::string localVar{std::move(a)};
    //std::string localVar{std::forward<T>(a)};
    //-> std::forward를 사용한 경우 두 경우 모두 good 출력
    std::cout << localVar << std::endl;
}
int main()
{
	int a = "good";
    
    //T는 string&, function argument는  string&
    printVar(a); // good 출력		
    
    //T는 string&&, function argument는 string
    printVar(std::move(a)); // 아무것도 출력 안된다
    
 
    //std::forward() 사용 시 출력 결과
    printVar(a); // good 출력	
    printVar(std::move(a)); // good 출력
    std::cout<< a << std::endl; // 아무것도 출력 안된다.
    return 0;
}
  • 예시 3에서 std::move() 동작
  • 예시 3에서 std::forward() 동작

Instantiation

  • type instantiation은 implicit한 특징을 가진다
    • multiple type parameter -> 예시 std::find
    • non type paramter -> 예시 std::array
    • parameter pack
  • 예시 코드
#include<iostream>

//multiple type parameter -> 두개 이상의 typename
template<typename IT, typename T>
IT foo1(IT first, IT last, const T& value)
{
	while (first != last)
	{
		if (*first == value)
		{
			return first;
		}
		first++;
	}
	return last;
}

//non type parameter-> typename 이외의 일반 타입 함께 쓰기
template<typename T, std::size_t N>
T foo2(T a)
{
	return N * a;
}

//parameter pack -> 갯수를 지정 안함
template<typename ...T>
void foo3(T && ... args)
{
	(std::cout << ... << args) << std::endl;
}

int main()
{
	//non type 예시
	std::cout << foo2<double, 4>(2.0) << std::endl;
    
	//parameter pack 예시
	foo3(1, 2, 3);
	foo3("ABC", "good");

	return 0;
}
  • template build 시에 모든 선언과 정의를 헤더파일에 해주지 않으면, implicit한 특징으로 인해 오류가 발생한다.
    • 그럼에도 불구하고 헤더파일에서 정의하기 싫다면 type explicit instantiation을 하면 된다.
  • type explicit instantiation 예시 (foo.h)
template<typename T>
T foo(T a);
  • type explicit instantiation 예시 (foo.cpp)
template<typename T>
T foo(T a)
{
	return a * 2;
}
template int foo<int>(int); //type explicit instantiation
  • type explicit instantiation 예시 (main.cpp)
#include <iostream>
#include "foo.h"

int main()
{
	std::cout << foo<int>(1) << std::endl;
	//std::cout << foo<double>(1.0) << std::endl; 컴파일 에러!
    //-> int에 대한 type explicit instantiation만 했기 때문에
	return 0;
}

Various Templates

  • Class template
    • std::vector
      std::vector<int> vecInt;
      std::vector<Widget> vecWidget;
  • Aliasing template
    • C++11 이전의 typedef와 유사하다.
      using myType = std::vector<std::array<uint8_t,64>>;
    • 사용 예시
#include<iostream>
#include<vector>

template<typename T>
using myKey = std::vector<std::array<T, 64>>

int main()
{
	using myInt = int;
	myInt a{ 10 };
	std::cout << a << std::endl;

	//Aliasing template 예시
	myKey<int> intKeys;
	myKey<double> doubleKeys;

	return 0;
}
  • Variable template
    • C++20 부터는 mathematical constants를 추가했다.
      pi_v 라는 variable template가 만들어져 있다.
    • 사용 예시
#include<iostream>

template<class T>
constexpr T pi = T(3.1415926535897932385L);

int main()
{
	int intPi = pi<int>;
	float floatPi = pi<float>;

	return 0;
}

Template Concept

  • C++20 부터 도입되었고 더 안전한 template programming을 위해 생겼다.
  • 코딩을 할 때 원하는 파라메터에 대한 정보를 붙여서(주석이나 이름 등) 프로그래밍을 해주어도 사용하는 사람의 입장에서는 실수를 하여 잘못된 값을 넣을 여지가 존재한다. 그러나 컴파일은 제대로 된다면 원치않는 결과가 발생할 것이다.
#include<iostream>
#include<string>

template<typename T>
T sum(T a, T b)
{
	return a + b;
}
int main()
{
	//아래 두개의 코드 모두 컴파일이 제대로 된다.
    //그러나 프로그래머는 sum은 int만 되도록 만들고 싶을때
    //-> template concept 사용 (컴파일 에러를 발생시켜준다)
	std::cout << sum<int>(10,10) << std::endl; 
    std::cout << sum<std::string>("abc", "def") <<std::endl;
}
  • template concept 예시
    • std::integral, std::floating_point, requires 등
#include<iostream>
#include<string>
#include<concepts>

//std::integral이용
template<typename T>
T sum1(T a, T b)
{
	static_assert(std::integral<T>, "only integral types");
	return a + b;
}

//requires 이용
template<typename T>
T sum2(T a, T b) requires std::integral<T>
{
	return a + b;
}

int main()
{
	std::cout << sum1<int>(10, 10) << std::endl; //ok
	//std::cout << sum1<std::string>("abc", "def") << std::endl; 컴파일 에러
	std::cout << sum2<int>(10, 10) << std::endl; //ok
	//std::cout << sum2<std::string>("abc", "def") << std::endl; 컴파일 에러
	return 0;
}
  • 사용자 지정 concept 예시
//사용자 지정 concept -> int와 float까지 가능하게 해준다.
template<typename T>
concept Summable = std::integral<T> || std::floating_point<T>;

//사용자 지정 concept 반영 템플릿
template<typename T>
T sum(T a, T b) requires Summable<T>
{
	return a + b;
}

//operator를 통한 concept 지정 -> - 연산이 없는 경우 컴파일 에러 발생시킴 (ex: string)
template<typename T>
concept Addable = requires(T x)
{
	x + x;
	x - x;
};

0개의 댓글