C++ 템플릿의 타입 추론과 인스턴스화
네이버 블로그에 작성한 Modern CPP — Templates (2025-05-07)를 바탕으로 설명과 예제를 보완했다.
함수 템플릿은 타입 등을 매개변수로 받아 함수의 형태를 정의한다. 모든 타입에 같은 연산을 적용할 수 있다는 뜻은 아니다. 타입 추론이 성공한 뒤에도 선택된 타입에 대해 함수 본문의 연산이 유효해야 한다.
정상적으로 동작하는 예제
다음 C++17 예제에서는 정수·실수·문자열에 같은 함수 템플릿을 사용한다.
#include <iostream>
#include <string>
template <typename T>
T add(T a, T b) {
return a + b;
}
int main() {
std::cout << add(1, 2) << '\n';
std::cout << add(2.5, 5.0) << '\n';
std::cout << addstring{"ab"}, std::string{"cd"} << '\n';
std::cout << add<double>(1, 2.5) << '\n';
return 0;
}
c++ -std=c++17 -Wall -Wextra -pedantic templates.cpp -o templates
./templates
3
7.5
abcd
3.5
add(1, 2)에서는 인자로부터 T=int를 추론한다. 마지막 줄은 T=double을 명시했기 때문에 첫 인자도 double로 변환할 수 있다.
실패하는 이유를 나눠 보기
다음 두 호출은 컴파일 실패를 확인하기 위한 예제다. 위의 정상 프로그램에 하나씩 추가해 진단 메시지를 비교한다.
// 의도적인 컴파일 오류
add(1, 2.5);
add("ab", "cd");
첫 번째 호출에서는 같은 T에 대해 int와 double이 각각 추론된다. 이 선언은 두 타입을 자동으로 공통 타입에 맞춰 주는 계약이 아니다.
두 번째 호출은 값 매개변수에서 문자열 리터럴이 const char*로 변환된다. 타입 추론 자체는 가능하지만 포인터 두 개의 +는 유효하지 않으므로 함수 본문을 인스턴스화할 때 실패한다. 문자열 연결에는 위의 std::string 예제를 쓴다.
인스턴스화는 런타임 이벤트가 아니다
암시적 인스턴스화는 함수의 정의가 필요한 등의 조건에서 컴파일 과정 중 일어난다. “프로그램이 실행되다가 처음 호출하는 순간 컴파일된다”는 뜻이 아니다. 명시적 인스턴스화도 있고, 최적화로 함수가 인라인될 수 있으므로 최종 실행 파일에 타입별 독립 함수가 항상 하나씩 보인다고 보장할 수도 없다.
다른 번역 단위에서 템플릿을 사용하려면 필요한 정의가 보여야 하는 경우가 많다. 그래서 일반적으로 정의를 헤더에 둔다. 구현을 .cpp에 숨길 때는 지원할 타입을 명시적으로 인스턴스화하는 등 별도 전략이 필요하다.
템플릿이 요구하는 연산을 계약으로 만들기
이 예제의 요구는 두 값에 +를 적용할 수 있고 결과를 T로 반환할 수 있다는 것이다. 실제 라이브러리에서는 참조·복사 비용·반환 타입까지 정해야 한다. C++20의 concepts는 요구 조건을 표현하는 도구지만, 템플릿을 쓰기 위해 처음부터 모든 기법을 도입할 필요는 없다.