01. Introduction
# Template의 종류
Function template
Class template
Aliasing template
Variable template
C++ 20부터 Template에 concept이 도입되면서
템플릿 프로그래밍 시 실수를 줄여준다
# Function template
함수의 인자 타입에서 함수 오버로딩을 제공하는 경우
가장 유용하게 사용된다
템플릿 함수는 미리 모든 것이 만들어지는 것이 아니라
넘겨지는 인자의 타입에 따라서
그때 함수들을 생성하며 컴파일이 되고
문제가 생기면 컴파일 에러가 발생한다
# Function template의 instantiation
Function template은 코드로 존재하다가
사용을 할 때, 그 타입에 맞는 함수가 컴파일이 되면서
바이너리 혹은 어셈블리로 만들어진다
02. Deduction
# Template type deduction
Template type deduction은 auto 키워드를 통해 만들어진다
# R-value Ref
함수의 인자로 R-value Ref를 받는 형태로 만들고
L-value를 넘겨주게 되면, 컴파일 에러가 발생한다
하지만 Template의 type deduction을 이용하면 에러가 발생하지 않는다
#include <iostream>
#include <memory>
void PrintLRef(int& a)
{
std::cout << a << std::endl;
}
void PrintRRef(int&& a)
{
std::cout << a << std::endl;
}
int main()
{
int a = 1;
PrintRRef(a);// 에러 발생
return 0;
}
# Forward Reference(Universal Reference)
Function Template의 인자로
R-value Ref를 받는 형태를 만들면
이는 R-value Ref가 아닌 Forward Reference(Universal Reference)라고 부른다
그 이유는 L-value Ref가 되기도 하고 R-value Ref 되기도 하기 때문이다
#include <iostream>
#include <memory>
template <typename T>
void PrintFRef(T&& a)
{
std::cout << a << std::endl;
}
int main()
{
int a = 1;
PrintFRef(a);
PrintFRef(std::move(a));
return 0;
}
# std::forward<T>()
템플릿 함수의 인자로 R-value Ref가 넘어오면 괜찮지만
L-value Ref가 넘어가는 경우
아래와 같이 std::move를 이용하면 소유권이 상실되면서
문제가 발생한다
이를 해결하기 위해서는 std::forward를 이용하면
L-value Ref일 경우 Copy
R-value Ref일 경우 Move가 된다
// 문제가 발생하는 경우
#include <iostream>
#include <string>
template <typename T>
void PrintFRef(T&& a)
{
std::string localVar{ std::move(a) };
std::cout << localVar << std::endl;
}
int main()
{
std::string a = "abc";
PrintFRef(a);// 이 과정에서 std::move가 발생해서 a는 "abc"에 대한 소유권이 사라진다
PrintFRef(std::move(a));// 따라서 아래 과정에서는 a에는 아무것도 없는 상태로 출력이 된다
return 0;
}
//std::forward를 통해 문제 해결
#include <iostream>
#include <string>
template <typename T>
void PrintFRef(T&& a)
{
std::string localVar{ std::forward<T>(a) };
std::cout << localVar << std::endl;
}
int main()
{
std::string a = "abc";
PrintFRef(a);// copy
PrintFRef(std::move(a));// move
return 0;
}
03. Instantiation
# Multiple type parameters
template <typename T1, typename T2>
# Non type parameter
template <typename T, std::size_t N>
# parameter pack
C++ 17부터 지원
#include <iostream>
template <typename ...T>
void parameterPack(T&& ... args)
{
(std::cout << ... << args) << '\n';
}
int main()
{
parameterPack(1, 2, 3);
parameterPack("ABC", "DEF");
return 0;
}
# Template Build (Instantitation)
definition code만 존재하고
실제 template function 혹은 template class가 호출될 때
Instatiation이 된다
이러한 특성 때문에 일반적인 header와 cpp 파일에 대한 접근을 바꿔야 한다
template declaration을 header에 넣고
template definition(implementation)을 cpp에 넣는다면,
template definition(implementation)이 작성된 cpp는
코드만 있을 뿐 실제로 어떤 타입으로 만들지 알 수가 없기 때문에
아무것도 컴파일하지 않는다
따라서
template declaration과 definition(implementation) 모두
header에 넣는 것이 일반적이다
혹은
아래의 코드처럼
해당 cpp 파일에 type explicit instantiation을 작성하는 방법도 있다
type explicit instantiation은
강제적으로 특정 타입의 바이너리 파일을 오브젝트 파일에 넣어달라고 해주는 것이다
// foo.h
#pragma once
template <typename T>
T foo(T a);// 선언
// foo.cpp
template <typename T>
T foo(T a)// 구현
{
return a * 2;
}
template int foo<int>(int);// explicit template int
template double foo<double>(double);// explicit template int
# type explicit instantiation의 Build
type explicit instantiation을 사용하지 않으면
컴파일러는 아무것도 컴파일하지 않고
template <typename T>
T foo(T a)
{
return a * 2;
}
//template int foo<int>(int);// explicit template int
//template double foo<double>(double);// explicit template int
int main()
{
return 0;
}
type explicit instantiation을 사용하면
해당 함수를 호출하지 않음에도 불구하고
컴파일러가 특정 타입의 template function을 Instatiation 해준다
template <typename T>
T foo(T a)
{
return a * 2;
}
template int foo<int>(int);// explicit template int
template double foo<double>(double);// explicit template int
int main()
{
return 0;
}
# 정리
template은 특이하게
type이 정해지는 순간 instantiation이 되기에
declaration과 implementation이 전부 헤더 파일에 있어야 한다
일반적으로는 type을 보고 implicit 한 위와 같은 경우가 아닌
explicit 하게 어떤 type을 사용할지
type explicit instantiation을 이용하면
그에 맞는 template 함수나 클래스를 instantiation 한다
04. Various Templates
# Class template
클래스의 멤버 변수 및 함수에서 사용 가능
# Aliasing template
typedef 혹은 using을 사용해서 Aliasing template 구현 가능
#include <iostream>
#include <array>
#include <vector>
template <typename T>
using aliasingTemplate = std::vector<std::array<T, 64>>;
int main()
{
aliasingTemplate<float> floatAT;
aliasingTemplate<double> doubleAT;
}
# Variable template
#include <iostream>
template<class T>
constexpr T pi = T(3.1415926535897932385L); // variable template
int main()
{
int intPi = pi<int>;
int floatPi = pi<float>;
int doublePi = pi<double>;
}
05. Template Concept
C++ 20부터 템플릿에 concept가 도입이 되면서
템플릿을 더 safer하고 intuitive하게 만들어준다
requires를 통해 type의 제한을 둘 수 있다
requires에서 사용하는 type은 아래 사이트를 통해 확인할 수 있다
https://en.cppreference.com/w/cpp/concepts
# 사용 예시 1
#include <iostream>
#include <concepts>
#include <string>
template<typename T> requires std::integral<T>
T sum(T a, T b)
{
return a + b;
}
int main()
{
std::cout << sum<int>(10, 10) << std::endl;
std::cout << sum<uint64_t>(30, 40) << std::endl;
std::cout << sum<std::string>("abc", "def") << std::endl;// 에러
}
# 사용 예시 2 - 직접
직접 type을 지정할 수도 있다
#include <iostream>
#include <concepts>
#include <string>
template<typename T>
concept MyConceptType = std::integral<T> || std::floating_point<T>;
template<typename T> requires MyConceptType<T>
T sum(T a, T b)
{
return a + b;
}
int main()
{
std::cout << sum<int>(10, 10) << std::endl;
std::cout << sum<uint64_t>(30, 40) << std::endl;
std::cout << sum<std::string>("abc", "def") << std::endl;// 에러
}
# 사용 예시 3 - 특정 조건
#include <iostream>
#include <concepts>
#include <string>
template <typename T>
concept Addable = requires (T x)
{
x + x;
x - x;
};// + 연산자와 - 연산자가 지원되는 타입만 가능하도록 만들어 줌
template<typename T> requires Addable<T>
T sum(T a, T b)
{
return a + b;
}
int main()
{
std::cout << sum<int>(10, 10) << std::endl;
std::cout << sum<uint64_t>(30, 40) << std::endl;
std::cout << sum<std::string>("abc", "def") << std::endl;// 에러
}
'C++ > [노코프] C++' 카테고리의 다른 글
[C++ NOTE] Types (0) | 2024.04.01 |
---|---|
[C++ NOTE] Functional Programming (0) | 2024.03.31 |
[C++ NOTE] Smart Pointer (0) | 2024.03.29 |
[C++ NOTE] Inheritance (0) | 2024.03.28 |
[C++ NOTE] OOP (0) | 2024.03.27 |