9. Generic Programming과 템플릿 (4) - 템플릿 인자 & 특수화
Generic Programming using Template
◆ 템플릿의 다중 매개변수
● 서로 다른 이름을 사용하여 다중 매개변수를 정의 가능
● 매개변수가 다르다면 서로 타입이 다를 수 있다
template <typename T1, typename T2>
void func(T1 a, T2 b)
{
std::cout << a << " " << b;
}
func<int, double>(10, 20.05);
// T1은 int 이고 T2는 double 인 func 함수가 생성된다.
func('A', 12.4);
// 타입을 명시하진 않았지만, T1은 char 이고 T2가 double 인 것이 명확하므로 해당하는 함수가 생성된다.
T1은 int 이고 T2는 double 인 func 함수가 생성된다.
타입을 명시하진 않았지만, T1은 char 이고 T2가 double 인 것이 명확하므로 해당하는 함수가 생성된다.
📘 핵심 요약: 템플릿의 다중 매개변수 정의
✅ 기본 구조
template <typename T1, typename T2>
void func(T1 a, T2 b) {
std::cout << a << " " << b;
}
- 템플릿 인자 T1, T2는 서로 다른 **자료형(Type)**을 받을 수 있음
- 호출 시 컴파일러가 각 인자의 타입을 추론하거나,
사용자가 명시적으로 <int, double>처럼 지정할 수 있음
✅ 사용 예시
func<int, double>(10, 20.05); // T1=int, T2=double
func('A', 12.4); // T1=char, T2=double (추론)
➡️ 컴파일 타임에 자동 생성되는 함수:
void func(int a, double b) { std::cout << a << " " << b; }
void func(char a, double b) { std::cout << a << " " << b; }
❓ 질문: T1과 T2가 같은 타입이어도 괜찮은가?
✔️ 완전히 괜찮다.
template <typename T1, typename T2>
void func(T1 a, T2 b) {
// ...
}
func<int, int>(10, 20); // 문제 없음
- T1과 T2가 같은 타입이더라도 문법적으로 전혀 문제가 없어
- 다만 같은 타입이면 template <typename T> 하나만 써도 충분하기 때문에,
굳이 나누지 않는 경우가 많다.
✅ 언제 다중 매개변수가 필요할까?
상황 | 설명 |
인자 타입이 다를 때 | Max(a, b)를 타입이 다른 두 개로 받고 싶을 때 |
반환형이 a의 타입이거나 b의 타입과 다를 때 | SFINAE나 decltype 같은 고급 문법과 결합될 때 |
함수 내부에서 각 인자를 다르게 처리할 때 | 예: func(string, int)이면 문자열을 먼저 출력, 숫자 뒤에 출력 등 |
💡 실전 팁
- 같은 타입이면 단일 매개변수 템플릿이 더 깔끔함
template <typename T>
T max(T a, T b);
- 다른 타입도 받을 가능성이 있다면 다중 매개변수로 정의
template <typename T1, typename T2>
auto max(T1 a, T2 b) -> decltype((a > b) ? a : b);
✅ 정리
항목 | 설명 |
template<typename T1, typename T2> | 두 개의 독립적인 템플릿 타입 인자 정의 |
T1과 T2는 같아도 됨 | 단지 둘 다 int일 수도 있음 (문법상 문제 없음) |
실전에서는? | 다를 가능성이 있는 경우에만 T1, T2로 분리하는 게 일반적 |
◆ 템플릿의 특수화
● 특정 자료형에 대해서는 템플릿을 사용하지 않고 별도 구현한 함수를 사용하도록 구현 가능
☞ 즉, string 타입에 대해 Min 함수를 호출할 때는 위의 함수가 아닌 아래 함수가 실행됨
template <typename T>
T Min(T a, T b) {
return (a < b) ? a : b;
}
template<>
std::string Min(std::string a, std::string b)
{
return (a.length() < b.length()) ? a : b;
}
std::string type 의 인자에 대해 사용하는 (명시적) 특수화
📘 핵심 요약: 템플릿의 특수화
✅ 기본 템플릿 함수
template <typename T>
T Max(T a, T b) {
return (a > b) ? a : b;
}
- 모든 타입 T에 대해 작동하는 일반적인 형태의 템플릿 함수
✅ 특수화: 특정 타입(std::string)에 대해 별도로 정의
template<>
std::string Max(std::string a, std::string b) {
return (a.length() > b.length()) ? a : b;
}
- template<>는 “특수화”임을 명시
- std::string 타입에 대해서는 length()를 기준으로 비교
- 호출:
Max<std::string>(e, f); // 특수화된 string 버전 호출됨
❓ Q1. #include <string>을 해야 << 출력이 되는 이유?
✔️ C++에서 std::string은 클래스이고, 그에 맞는 operator<<도 std::string을 위한 오버로딩이 <string> 헤더 안에 정의되어 있음.
// string 헤더 내부에 이런 코드가 있음
std::ostream& operator<<(std::ostream&, const std::string&);
그래서 헤더를 포함하지 않으면 std::cout << e; 에서
std::string에 맞는 연산자를 못 찾아서 컴파일 에러가 발생함.
❓ Q2. 별다른 특수화 없다면 cde > abcd 비교 시 왜 cde가 더 크다고 나오나?
✔️ ASCII 코드 기준으로 문자열 비교가 일어나기 때문이다.
return (a > b) ? a : b;
- 이때 a > b는 문자열을 사전순(lexicographical order)으로 비교한다.
- 'c' > 'a' → true
→ 즉, 문자열 자체가 크다는 건 첫 글자가 더 나중이면 더 큰 것
그래서 "cde" > "abcd" → true
❓ Q3. 그럼 string 말고 다른 타입도 특수화 가능한가?
✔️ 물론이다! 특수화는 무제한으로 정의 가능하다.
// 기본 템플릿
template <typename T>
T Max(T a, T b) {
return (a > b) ? a : b;
}
// string 특수화
template <>
std::string Max(std::string a, std::string b) {
return (a.length() > b.length()) ? a : b;
}
// bool 특수화
template <>
bool Max(bool a, bool b) {
std::cout << "[bool 특수화]\n";
return a || b; // true가 우선
}
➡️ 호출 결과:
Max<std::string>("a", "abc"); // 길이 비교
Max<bool>(true, false); // OR 논리 연산 사용
❓ Q4. template<>를 안 쓰면 어떻게 되나?
std::string Max(std::string a, std::string b);
이렇게 썼는데 template<>를 생략했다면?
- 이건 템플릿 특수화가 아니라 그냥 일반 오버로딩 함수로 간주됨
- Max<std::string>(e, f)처럼 명시적으로 템플릿 호출하면 → 템플릿만 찾음 → 오버로딩된 함수 무시 → 컴파일 에러 발생
- 반대로 Max(e, f)처럼 템플릿 호출이 아닌 일반 함수 호출이면
일반 함수 오버로딩이 적용되어 string용 Max()가 호출될 수 있음
호출 방식 | 결과 |
Max<std::string>(e, f) | template<> std::string Max(...) 특수화 호출됨 |
Max(e, f) | 오버로딩된 일반 함수가 있다면 그쪽이 우선됨 |
✅ 정리:
항목 | 설명 |
특수화 | 특정 타입에 대해 별도의 템플릿 정의 |
문법 | template<>를 붙이고, 타입명을 고정한 함수 작성 |
다중 특수화 | 가능함. string, bool, custom 타입 모두 가능 |
일반 함수 오버로딩과 차이 | 템플릿 호출시에는 오버로딩이 무시됨. 특수화가 없으면 에러 발생 가능 |
<string> 포함 이유 | operator<< 정의 포함되어 있어서 출력 가능해짐 |
ChatGPT 로 만든 예제
📄 예제: 1_Function_Template.cpp
🎯 설명
서로 다른 타입에 대해 범용적으로 동작하는 Max/Min 함수 구현
#include <iostream>
using namespace std;
// 템플릿 함수: 최대값 반환
template <typename T>
T Max(T a, T b) {
return (a > b) ? a : b;
}
// 템플릿 함수: 최소값 반환
template <typename T>
T Min(T a, T b) {
return (a < b) ? a : b;
}
int main() {
cout << "int Max: " << Max(10, 20) << endl;
cout << "double Min: " << Min(3.14, 2.71) << endl;
cout << "char Max: " << Max('a', 'z') << endl;
cout << "bool Max: " << Max(true, false) << endl; // true > false
}
📄 예제: 1_1_Function_Template_Specialization.cpp
🎯 설명
함수 템플릿의 명시적 특수화
기본 비교 대신 string, bool에 대해 별도 동작 구현
#include <iostream>
#include <string>
using namespace std;
// 기본 템플릿
template <typename T>
T Max(T a, T b) {
return (a > b) ? a : b;
}
// string 타입 특수화: 길이 기준으로 비교
template <>
string Max<string>(string a, string b) {
cout << "[string 특수화 호출]" << endl;
return (a.length() > b.length()) ? a : b;
}
// bool 타입 특수화: true 우선 반환
template <>
bool Max<bool>(bool a, bool b) {
cout << "[bool 특수화 호출]" << endl;
return a || b;
}
int main() {
cout << Max(3.14, 1.618) << endl; // 일반 템플릿
cout << Max<string>("short", "longer!") << endl; // 특수화
cout << Max<bool>(false, false) << endl; // 특수화
}
Generic Programming 과 템플릿
◆ Generic Programming 이란 : 타입에 관계없이 동작하는 코드 작성 개념
● Macro를 활용한 Generic Programming : 매크로를 활용하여 코드를 대처하는 방식, 주의가 필요
◆ 템플릿을 활용한 Generic Programming
● 함수 템플릿 : template<typename T>, 템플릿 인자와 특수화
● 클래스 템플릿
📘 핵심 정리: 함수 템플릿의 구조와 활용
🎯 Generic Programming의 핵심 개념
타입에 관계없이 동일한 논리로 동작할 수 있는 코드를 작성하는 방식
template <typename T>
T Max(T a, T b) {
return (a > b) ? a : b;
}
- T는 템플릿 인자 (타입 이름 대신 사용)
- template <typename T>는 함수가 템플릿 함수임을 명시
- 사용 시점에 컴파일러가 타입 추론 또는 명시적 타입 지정을 통해 코드 생성
🎯 템플릿 함수 사용 예
Max(10, 20); // T = int, 자동 추론
Max<double>(1.5, 3.7); // T = double, 명시적 지정
- 다중 인자를 사용할 수 있음
- T1, T2가 다른 타입일 수 있음
- decltype()을 통해 자동 반환 타입 추론 가능
🎯 특수화 (Explicit Specialization)
특정 타입에는 일반 템플릿이 아닌 다른 로직을 적용하고 싶을 때 사용
template <>
std::string Max<std::string>(std::string a, std::string b) {
return (a.length() > b.length()) ? a : b;
}
- std::string에 대해서는 일반 비교 대신 길이 비교로 특수화
- 호출 시 Max<std::string>(e, f) → 이 특수화 버전이 사용됨
🎯 사용자 정의 타입 사용 시 주의점
class Point {
int x, y;
public:
bool operator>(const Point& rhs) const { return x + y > rhs.x + rhs.y; }
};
📌 템플릿 함수 내부에서 a > b를 사용하려면,
사용하는 타입에 대해 해당 연산자(operator>)가 오버로딩되어 있어야 함
- 그렇지 않으면 컴파일 시점에서 템플릿 함수 인스턴스화 실패 → 컴파일 에러 발생
✅ 최종 요약
요소 | 요약 |
템플릿 정의 | template <typename T> + T를 타입 자리에서 사용 |
다중 인자 | T1, T2 등 서로 다른 타입 가능 |
특수화 | 특정 타입에 대해 별도 함수 정의 (template<>) |
사용자 정의 타입 | 연산자 오버로딩이 되어 있어야 템플릿 함수 사용 가능 |
https://youtu.be/amusCkEpX3E?si=NJl170DcgXLPurFO
좋은 강의 감사합니다