[2026] C++ CTAD | 클래스 템플릿 인자 추론 가이드
이 글의 핵심
C++17 CTAD로 pair·vector 등 클래스 템플릿 인자를 생략하는 방법. 사용자 정의 클래스와 추론 가이드(deduction guide) 실무 팁을 정리합니다.
들어가며
C++17에서 도입된 CTAD(Class Template Argument Deduction)는 클래스 템플릿 인자를 자동으로 추론하여 코드를 간결하게 만들어줍니다.
실무에서 마주한 현실
개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.
1. CTAD 기본
C++14 vs C++17
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// C++14: 타입 명시
std::pair<int, double> p(1, 3.14);
std::vector<int> vec = {1, 2, 3};
// C++17: 타입 추론
std::pair p(1, 3.14);
std::vector vec = {1, 2, 3};
표준 라이브러리
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <vector>
#include <map>
#include <tuple>
#include <array>
int main() {
std::pair p(1, "Hello");
std::tuple t(1, 2.0, "Hi");
std::vector vec = {1, 2, 3};
std::map m = {{"a", 1}, {"b", 2}};
}
2. 커스텀 클래스 CTAD
기본 예제
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
template<typename T>
class Container {
T value;
public:
Container(T v) : value(v) {}
T get() const { return value; }
};
int main() {
Container c(42);
Container c2(3.14);
Container c3("Hello");
std::cout << c.get() << std::endl; // 42
std::cout << c2.get() << std::endl; // 3.14
std::cout << c3.get() << std::endl; // Hello
}
복사 생성자
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
template<typename T>
class Wrapper {
T value;
public:
Wrapper(T v) : value(v) {}
Wrapper(const Wrapper& other) : value(other.value) {}
T get() const { return value; }
};
int main() {
Wrapper w1(42);
Wrapper w2 = w1;
std::cout << w2.get() << std::endl; // 42
}
3. 추론 가이드 (Deduction Guides)
기본 추론 가이드
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
template<typename T>
class MyClass {
T value;
public:
MyClass(T v) : value(v) {}
};
template<typename T>
MyClass(T) -> MyClass<T>;
int main() {
MyClass obj(42);
std::cout << "OK" << std::endl;
}
커스텀 추론 가이드
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
#include <string>
template<typename T>
class Container {
T value;
public:
Container(T v) : value(v) {}
T get() const { return value; }
};
Container(const char*) -> Container<std::string>;
int main() {
Container c("Hello");
std::cout << c.get() << std::endl; // Hello
}
복잡한 추론 가이드
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
#include <vector>
template<typename T>
class Matrix {
std::vector<std::vector<T>> data;
public:
Matrix(std::vector<std::vector<T>> d) : data(d) {}
};
template<typename T>
Matrix(std::vector<std::vector<T>>) -> Matrix<T>;
int main() {
Matrix m({{1, 2}, {3, 4}});
std::cout << "OK" << std::endl;
}
4. 자주 발생하는 문제
문제 1: 모호한 추론
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
template<typename T>
class Container {
public:
Container(T value) {
std::cout << "T value" << std::endl;
}
Container(T* ptr, size_t size) {
std::cout << "T* ptr" << std::endl;
}
};
int main() {
int arr[5];
Container<int> c(arr, 5);
}
문제 2: 초기화 리스트
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <vector>
#include <iostream>
int main() {
std::vector vec{1, 2, 3};
std::vector vec2{vec};
std::cout << vec2.size() << std::endl; // 1
std::vector vec3(vec);
std::cout << vec3.size() << std::endl; // 3
}
문제 3: const char*
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <utility>
#include <string>
#include <iostream>
int main() {
std::pair p("Hello", "World");
std::pair<std::string, std::string> p2("Hello", "World");
std::cout << p2.first << std::endl; // Hello
}
5. 추론 비활성화
explicit 생성자
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
template<typename T>
class NoDeduction {
public:
explicit NoDeduction(T value) {}
};
int main() {
NoDeduction<int> obj(42);
}
6. 실전 예제
예제 1: 스마트 포인터
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <memory>
#include <iostream>
template<typename T>
class SmartPtr {
T* ptr;
public:
SmartPtr(T* p) : ptr(p) {}
~SmartPtr() { delete ptr; }
T& operator*() { return *ptr; }
};
template<typename T>
SmartPtr(T*) -> SmartPtr<T>;
int main() {
SmartPtr p(new int(42));
std::cout << *p << std::endl; // 42
}
예제 2: 타입 안전 ID
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
#include <string>
template<typename T>
class TypedId {
std::string id;
public:
TypedId(std::string i) : id(i) {}
std::string get() const { return id; }
};
template<typename T>
TypedId(std::string) -> TypedId<T>;
struct User {};
struct Post {};
int main() {
TypedId<User> userId("U001");
TypedId<Post> postId("P001");
std::cout << userId.get() << std::endl; // U001
}
정리
핵심 요약
- CTAD: C++17 클래스 템플릿 인자 추론
- 추론 가이드: 커스텀 추론 규칙
- 표준 라이브러리: pair, vector, map 등
- 성능: 컴파일 타임, 런타임 오버헤드 없음
CTAD 장단점
| 장점 | 단점 |
|---|---|
| 간결한 코드 | 타입 명확성 감소 |
| 타입 안전 | 모호한 추론 가능 |
| 컴파일 타임 | C++17 이상 필요 |
실전 팁
- 타입이 명확할 때 CTAD 사용
- 모호한 경우 명시적 타입 지정
- 추론 가이드로 커스터마이징
const char*는std::string으로 변환 고려
다음 단계
- C++ Deduction Guides
- C++ if constexpr
- C++ Template