
함수 템플릿
예를들어 a,b중 큰 값을 return하는 함수가 있다고 가정해보자
int Max(int a, int b)
{
return (a < b) ? b : a;
}
하지만 double, long과 같은 다른 type으로 비교를 하고 싶다면 해당 타입별로 Max()를 전부 다 만들어야 한다 (특정 타입을 예상하지 못하기 때문에 함수 overaloading으로도 해결할 수 없음)
각각의 type별로 함수들을 전부 만들고 사용한다면 유지보수에 좋지 않고 DRY원칙도 위배하게 된다
이렇게 logic이 완전히 동일하지만 type이 다를때 template을 사용하여 하나의 함수로 전부 대응할 수 있게 한다 (코드 유지보수, 중복 코드 감소에 좋다)
C++의 template system은 다양한 데이터 타입에 따른 함수, 클래스 제작이 쉽도록 한다
template이란
template은 함수, class의 정의만 작성하고 특정 타입에 대해 사용될 때 실제 코드가 생성된다 (그렇기 때문에 함수 템플릿 자체는 컴파일되거나 실행되지 않는다, 실제 함수를 생성하는 용도이다, 실제로 생성된 함수가 컴파일되고 실행된다)
타입은 나중에 주어지기 때문에 함수, class를 구현할 때 타입을 미리 쓸 필요가 없다
그렇다면 Max()를 template을 이용하여 template function으로 만들어보자
template <typename T>
T max(T a, T b)
{
return (a < b) ? b : a;
}
template 키워드로 template임을 나타낸다, 이때 typename T의 T는 template에서 사용할 타입이 된다 (이때 이름은 꼭 T가 아니어도 됨)
typename 대신 class를 써도 되지만 typename을 사용하는걸 권장한다
보통 typename은 한 글자를 사용하는것이 일반적이다, T, U, V등을 사용하며 보통 T를 많이 사용한다
함수 템플릿 인스턴스화
위에서 정리했듯 함수 템플릿 자체는 컴파일, 실행되지 않는다, 이는 실제 함수를 생성하는 용도이다
함수 템플릿을 사용할 때는 다음과 같이 사용한다
함수 템플릿이름<타입>(매개변수);
Max<int>(1, 2);
여기서 < >안에 타입을 Template Argument(템플릿 인자)라고 한다
그냥 Max(1,2);는 타입을 컴파일러가 자동추론하지만 < >를 이용하면 해당 타입으로 강제할 수 있다
(만약 일반함수가 같은 형태로 존재한다면 일반함수를 먼저 호출한다)
int Max(int a, int b)
{
}
T Max(T a, T b)
{
}
//int Max()가 호출됨
이렇게 함수 템플릿을 사용하여 생성된 함수를 함수 인스턴스라고 한다
이때 함수 템플릿을 특수화 하여 생성된 함수 인스턴스는 한번만 생성되어 나중에 호출될 경우 해당 함수 인스턴스를 재사용한다
함수 템플릿 에러
타입에서 지원하지 않는 연산을 한다면 함수 템플릿 사용 시 error가 발생한다
template <class T>
T Foo(T a)
{
return a + 1;
}
std::string fooval{ "Hi" };
Foo(fooval);

함수 템플릿에서도 일반 함수와 마찬가지로 delete를 사용하여 이러한 에러를 방지할 수 있다
std::string Foo(std::string a) = delete;
이제 함수 템플릿 Foo를 사용할 때 std::string 타입은 사용할 수 없게 된다
여러개의 template type
예를 들어 다음과 같은 코드를 실행한다고 가정해보자
template <typename T>
T Max(T a, T b)
{
return (a < b) ? b : a;
}
Max(1, 2.5f);
다음과 같은 에러를 발생시킨다

< >를 사용하여 명확한 template type을 제시하지 않았기 때문에 컴파일러는 int, float type으로 되어 있는 함수 템플릿을 찾는다, 하지만 존재하지 않기 때문에 에러가 발생하는 것이다
한마디로 T하나로는 int, float 2개의 type을 처리할 수 없기 때문에 함수 템플릿 인스턴스화가 불가능한 것이다
template 타입 추론과정에서는 타입 변환이 이루어지지 않는다
(template의 단순성을 유지하기 위해, 모든 인자가 동일한 타입이어야 하는 경우를 보장하기 위해)
위와 같은 문제는 총 3가지 정도의 방법으로 해결이 가능하다
1. 명시적 변환
Max(static_Cast<float>(1), 2.5f);
가독성이 좋지 않다
2. 명시적 template type 지정
Max<float>(1, 2.5f);
개발자가 직접 타입을 지정해줘야 한다는 단점이 있음 (물론 타입을 명확히 할 수 있다는 장점이자 단점이라고 생각함)
3. 템플릿 인자 여러개 만들기
template <typename T, typename U>
T Max(T a, U b)
{
}
Max(1, 2.5f);
이제 T는 int U는 float으로 분리해서 타입이 결정된다
하지만 반환형이 T이기 때문에 값은 2로 나오게 된다, 따라서 반환형으로는 auto를 사용하는걸 권장한다
auto와 함께 std::common_type_t<>도 좋은 방법이다
#include <type_triats>
template <typename T, typename U>
auto Max(T a, U b) -> std::common_type_t<T, U>
{
}
C++20 이후의 축약형 함수 템플릿
C++20부터는 단순히 auto를 사용하여 축약형 함수 템플릿을 만들 수 있다
auto Max(auto a, auto b)
{
}
Max(1, 2.5f);
이는 독립적인 템플릿 타입 인자를 생성하는 것과 같다
non-type 템플릿 매개변수
non-type이란 타입이 아닌 상수 표현식 값을 전달받을 수 있는 고정된 타입의 템플릿 매개변수이다
non-type 매개변수는 아래와 같은 타입이 될 수 있다

다음과 같이 사용할 수 있다
template <int N> //보통 정수형 non-type template 인자의 이름으로 N을 많이 사용한다
void Foo()
{
std::cout << N << '\n';
}
Foo<10>(); //10출력
그렇다면 이러한 non-type 템플릿 매개변수는 어디에 자주 사용할까?
C++20에서는 함수의 매개변수로 constexpr을 사용할 수 없다 (일반, constexpr, consteval 함수 전부)
다음과 같은 코드가 있다고 가정해보자
void foo(double d)
{
assert(d >= 0.0 && "Under than 0.0");
}
foo(-5.0); //런타임 에러
이때 -5.0은 컴파일 타임에 검증할 수 있는 literal value이기 때문에 런타임 보다 컴파일 타임에 에러를 확인하는게 더 좋다, 따라서 컴파일 타임 assertion인 static_assert()를 사용하려면 매개변수가 constexpr이더야 한다
하지만 C++20에서 함수의 매개변수는 constexpr이 될 수 없고 이럴때 사용하는게 바로 non-type 템플릿 매개변수이다
template <double D>
void foo()
{
static_assert(D >= 0.0. "Under than 0.0");
}
다중 파일에서의 함수 템플릿 사용
만약 아래와 같은 상황이라고 가정해보자
//A.cpp
template <typename T>
T Foo(T a)
{
}
//main.cpp
template <typename T>
T Foo(T a); //forward declaration
int main()
{
Foo<int>(100);
}
이러한 코드를 돌리게 되면 LNK error가 발생하게 된다
main.cpp에서 Foo<>()를 호출하면 컴파일러는 A.cpp에서 Foo<>()의 정의를 연결해야 한다, 하지만 템플릿 함수는 호출 될 때 인스턴스화 되기 때문에 호출에 맞는 정의를 연결할 수 없어 LNK error가 발생하는 것이다
따라서 하나의 파일에 있는 템플릿 함수를 여러곳에서 사용하려면 템플릿 정의를 .h에서 정의하는것이 좋다
//A.h
template <typename T>
T Foo(T a)
{
}
//main.cpp
#include "A.h"
int main()
{
Foo<int>(100);
}
그렇다면 이러한 A.h가 이곳저곳에 include되면 ODR을 위배하지 않을까? 라고 생각할 수 있지만 템플릿 정의는 예외로 처리한다 -> 동일한 템플릿 정의가 여러 파일에 포함되어도 상관 없음
하지만 템플릿 자체는 inline이 아니고 템플릿으로 부터 생성된 함수 인스턴스가 인라인으로 처리된다