48. 클래스 템플릿 인자 추론(CTAD)과 추론 가이드
난이도 상내 리뷰 · C++
해설 · C++
해설 — 클래스 템플릿 인자 추론(CTAD)과 추론 가이드
난이도: 상
요약
C++17의 CTAD는 std::pair p{1, 2.5} 처럼 클래스 템플릿의 인자를 생성자 호출로부터 추론한다. 컴파일러는 생성자(및 사용자 정의 추론 가이드)로부터 "가상의 함수 템플릿 집합"을 만들어 오버로드 해석하듯 인자를 추론한다. 예측 출력은 true / 2 / 5 / true / 2.
핵심 개념
- CTAD: 클래스 템플릿 이름만 쓰고(
std::vector v{...}) 인자를 생략하면, 컴파일러가 이니셜라이저로부터 템플릿 인자를 추론한다.std::vector<int> v{...}를 손으로 쓰지 않아도 된다. - 추론 가이드(deduction guide):
Wrapped(const char*) -> Wrapped<std::string>;는 "생성자 인자가const char*이면T = std::string으로 추론하라"고 컴파일러에 알린다. 표준 라이브러리도 이 방식으로vector(It, It),pair등의 추론을 정의한다. - CTAD는 오브젝트 선언 시점에만 작동한다. 함수 인자, 상속,
new표현식 일부 등에는 제약이 있다.
단계별 내부 동작
- 가상 생성자 템플릿 생성: 클래스 템플릿의 각 생성자와 각 추론 가이드로부터 함수 템플릿을 만든다.
Wrapped의 경우:- 생성자로부터:
template<class T> Wrapped<T> F(T); - 가이드로부터:
Wrapped<std::string> F(const char*);
- 생성자로부터:
- 오버로드 해석: 실제 인자로 이 가상 함수들을 오버로드 해석한다. 더 특수한/정확히 맞는 후보가 이긴다.
각 줄:
- (1)
std::pair p{1, 2.5}→pair<int, double>.2.5는double. L1 =true. - (2)
std::vector v1{5, 5}→vector<int>. 중괄호 목록은std::initializer_list<int>{5,5}생성자를 우선 매칭 → 원소가 5, 5 인 크기 2 벡터. L2 =2. - (3)
std::vector v2(5, 5)→vector<int>. 괄호는vector(size_type count, const T& value)생성자에 매칭 → 값 5가 5개인 크기 5 벡터. L3 =5. - (4)(5)
Wrapped w{"hi"}→ 추론 가이드가const char*→Wrapped<std::string>로 추론. 생성자 후보Wrapped<const char*>보다 가이드가 선택된다. L4 =true,w.value는std::string("hi")이므로.size()=2. L5 =2.
흔한 오해·함정
{}vs()의 차이: CTAD 여부와 무관하게,std::vector<int>에서도{5,5}와(5,5)는 다른 생성자를 부른다. CTAD가 이 함정을 만든 게 아니라 드러낼 뿐이다. "동일 초기화"라 착각하면 원소 개수가 달라진다.- 추론 가이드 없으면: 가이드가 없으면
Wrapped w{"hi"}는Wrapped<const char*>로 추론되어value.size()자체가 컴파일 불가. 가이드가 의도를 바로잡는다. - 부분 CTAD 불가:
std::vector<> v처럼 일부만 비우는 문법은 없다. 전부 추론하거나 전부 명시. - 집계(aggregate) CTAD 는 C++20부터. C++17에서는 사용자 정의 생성자/가이드가 필요.
explicit생성자와 가이드의explicit여부가 복사 초기화(Wrapped w = ...) 가능성에 영향을 준다.
면접 포인트
- CTAD가 "가상 함수 템플릿 오버로드 해석"으로 구현된다는 점과 추론 가이드의 역할.
- 같은 이니셜라이저라도
{}(initializer_list 우선)와()가 다른 생성자를 부르는 이유. - 표준 컨테이너들이 반복자쌍·initializer_list 추론을 어떻게 가이드로 제공하는가.