최근 수정 시각 : 2025-09-17 05:07:24

C++/문법/템플릿

파일:관련 문서 아이콘.svg   관련 문서: C++
#!if 문서명2 != null
, [[함수형 프로그래밍]]
#!if 문서명3 != null
, [[메타 데이터]]
#!if 문서명4 != null
, [[]]
#!if 문서명5 != null
, [[]]
#!if 문서명6 != null
, [[]]

파일:상위 문서 아이콘.svg   상위 문서: C++/문법
프로그래밍 언어 문법
{{{#!folding [ 펼치기 · 접기 ]
{{{#!wiki style="margin: 0 -10px -5px; word-break: keep-all"
프로그래밍 언어 문법
C(포인터 · 구조체 · size_t) · C++(이름공간 · 클래스 · 특성 · 상수 표현식 · 람다 표현식 · 템플릿/제약조건/메타 프로그래밍) · C# · Java · Python(함수 · 모듈) · Kotlin · MATLAB · SQL · PHP · JavaScript(표준 내장 객체, this) · Haskell(모나드) ·
마크업 언어 문법
HTML · CSS
개념과 용어
함수(인라인 함수 · 고차 함수 · 콜백 함수 · 람다식) · 리터럴 · 문자열 · 식별자(예약어) · 상속 · 예외 · 조건문 · 반복문 · 비트 연산 · 참조에 의한 호출 · eval · 네임스페이스 · 호이스팅
기타
#! · == · === · deprecated · GOTO · NaN · null · undefined · 배커스-나우르 표기법
}}}}}}
프로그래밍 언어 목록 · 분류 · 문법 · 예제

1. 개요2. 템플릿 매개변수
2.1. 자료형 템플릿 매개변수2.2. 상수 템플릿 매개변수2.3. 중첩 템플릿 매개변수
3. 함수 템플릿
3.1. 추가 설명3.2. 함수 매개변수의 기본값3.3. 함수 인자 연역3.4. 강제 인라인
4. 변수 템플릿5. 기본 템플릿 인자값6. 명시적 템플릿 매개변수7. 템플릿 매개변수 순서8. 템플릿 인자 연역9. 완벽한 매개변수 전달10. 클래스와 멤버 템플릿
10.1. 멤버 함수 템플릿10.2. 데이터 멤버
11. 자가 연역12. 클래스 템플릿
12.1. 멤버 함수12.2. 생성자12.3. 연산자 오버로딩
13. 템플릿 연역 유도문14. 템플릿 특수화
14.1. 전체 특수화
14.1.1. 함수 템플릿 특수화
14.2. 부분 특수화
15. 함수 템플릿 오버로딩16. SFINAE
16.1. 예제1: 멤버 구현 여부16.2. 예제2: 멤버 자료형의 존재 여부16.3. 예제3: 메타 데이터16.4. 예제4: 자료형 트레잇 (Trait)
17. 메타 함수18. 가변 템플릿
18.1. 가변 함수 템플릿18.2. 예제1: 가변 인자 함수
18.2.1. 접힌 표현식18.2.2. 구현
18.3. 예제2: 가변 인자 구조체18.4. 예제3: 람다 표현식을 활용한 직렬화 함수
18.4.1. 개론: 직렬화에 대한 이해18.4.2. 서론: 매개변수 묶음 열거
18.4.2.1. 표준 라이브러리: integer_sequence
18.4.3. 본론: 템플릿 매개변수 묶음 확장18.4.4. 결론
18.5. 예제4: 가변 변수 템플릿18.6. 예제5: 가변 자료형 트레잇으로 구현하는 메타 함수
18.6.1. 표준 라이브러리: conjunction18.6.2. 정리
19. 메타 프로그래밍

1. 개요

[include(틀:C++ 요소, head_keyword=template,
template_p0=템플릿-매개변수1, template_p1=템플릿-매개변수2,
template_last_label=..., template_lnb=1,
fn_attribute=특성,
pre1_t=자료형, body_v=변수-식별자, label_last= \= 값, last=;)]

[include(틀:C++ 요소, head_keyword=template,
template_p0=템플릿-매개변수1, template_p1=템플릿-매개변수2,
template_last_label=..., template_lnb=1,
fn_attribute=특성, fn_attribute_lnb=1,
pre1_t=반환-자료형, body_f=함수-식별자,
arg1_t=매개변수1-자료형, arg1_param=매개변수1, arg2_t=매개변수2-자료형, arg2_param=매개변수2, arg_last_dots=1,
body_bopen=1, body_bclose=1 , last=;)]

[include(틀:C++ 요소, head_keyword=template,
template_p0=템플릿-매개변수1, template_p1=템플릿-매개변수2,
template_last_label=..., template_lnb=1,
cls_attribute=특성,
kw1=class, pre1_t=클래스-식별자,
last=;)]
템플릿 (Template)
템플릿은 사용자가 여러 자료형에 대하여 범용적인 코드를 작성할 수 있도록 돕는 기능이다. template과 꺽쇠 괄호 <> 안에 자료형을 매개변수로 전달할 수 있다. 기본적으로 템플릿에는 모든 자료형을 전달할 수 있다. 함수와 클래스에 템플릿이 사용되면 기존에 명시했던 자료형을 템플릿 매개변수로 대체할 수 있다. 결과적으로 하나의 자료형에 귀속된 코드가 아니라 범용적인 코드를 쓸 수 있게 해준다[1]. 또한 이렇게 전달된 자료형은 사용자가 자료형에 대한 정보를 읽거나 연산을 하는 데 쓸 수 있다[2].

C++의 시작부터 함께한 기능이지만 범용 함수나 범용 클래스를 만드는 것 이외의 기능은 겨우 C++11부터 추가되고 있다. 최근에는 템플릿과 일반 코드를 작성하는 과정이 서로 동질화되고 불합리하게 추가로 작성해야할 코드의 양도 적어지고 있다. 그러나 그걸 하기위해 익혀야 하는 기능은 매우 많으므로 어플리케이션을 개발하면서 일반화 프로그래밍이 필요한 부분부터 천천히 도입하는 것이 좋다. 적어도 다른 프로그래밍 언어에서 제네릭(Generic)이 쓰이는 정도만 익히는 것도 추천된다. 제네릭은 C++의 템플릿에겐 아주 사소한 기능이니 말이다.

2. 템플릿 매개변수

#!if head_comment!=null
{{{#57A64A,#5EBD46 }}}
#!if head_lnb!=null
##======================================= include and import
[br]
#!if import!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{import }}}}}}'''{{{#C8865E {{{<>}}}}}}{{{;}}}
#!if include!=null
##======================================= the keyword at head (can be `template`)
{{{#include <>}}}
#!if (head_keyword_available = head_keyword != null)
##======================================= template parameters begin
'''{{{#DodgerBlue,#CornFlowerBlue {{{template}}}}}}'''
#!if template_available = (template_p0 != null || template_v0 != null || template_p1 != null || template_v1 != null || template_p2 != null || template_v2 != null || template_p3 != null || template_v3 != null)
#!if template_available || template_last_label != null
##======================================= template parameter 0
##======================================= template parameter 0 concept
{{{<}}}{{{#!if template_concept0_available = (template_cpt0 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept0_params_available = (template_concept0_p0 != null || template_concept0_p1 != null || template_concept0_p2 != null || template_concept0_p3 != null)
{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{<}}}}}}{{{#!if template_concept0_params_available
##======================================= template parameter 0 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p0 != null || template_concept0_v0 != null) && (template_concept0_p1 != null || template_concept0_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p1 != null || template_concept0_v1 != null) && (template_concept0_p2 != null || template_concept0_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p2 != null || template_concept0_v2 != null) && (template_concept0_p3 != null || template_concept0_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept0_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept0_available
{{{ }}}}}}{{{#!if template_p0_available = (template_p0 != null)
{{{#!if !template_concept0_available
##======================================= template parameter 0 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v0_available = (template_v0 != null)
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}
##======================================= template parameter 0 finished
}}}{{{#!if (template_p0_available || template_v0_available) && (template_p1 != null || template_v1 != null)
##======================================= template parameter 1
{{{, }}}}}}{{{#!if template_concept1_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept1_params_available = (template_concept1_p0 != null || template_concept1_p1 != null || template_concept1_p2 != null || template_concept1_p3 != null)
{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{<}}}}}}{{{#!if template_concept1_params_available
##======================================= template parameter 1 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p0 != null || template_concept1_v0 != null) && (template_concept1_p1 != null || template_concept1_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p1 != null || template_concept1_v1 != null) && (template_concept1_p2 != null || template_concept1_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p2 != null || template_concept1_v2 != null) && (template_concept1_p3 != null || template_concept1_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept1_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept1_available
{{{ }}}}}}{{{#!if template_p1_available = (template_p1 != null)
{{{#!if !template_concept1_available
##======================================= template parameter 1 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}}}}{{{#!if template_v1_available = (template_v1 != null)
##======================================= template parameter 1 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if (template_p1_available || template_v1_available) && (template_p2 != null || template_v2 != null)
##======================================= template parameter 2
{{{, }}}}}}{{{#!if template_concept2_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept2_params_available = (template_concept2_p0 != null || template_concept2_p1 != null || template_concept2_p2 != null || template_concept2_p3 != null)
{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{<}}}}}}{{{#!if template_concept2_params_available
##======================================= template parameter 2 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p0 != null || template_concept2_v0 != null) && (template_concept2_p1 != null || template_concept2_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p1 != null || template_concept2_v1 != null) && (template_concept2_p2 != null || template_concept2_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p2 != null || template_concept2_v2 != null) && (template_concept2_p3 != null || template_concept2_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept2_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept2_available
{{{ }}}}}}{{{#!if template_p2_available = (template_p2 != null)
{{{#!if !template_concept2_available
##======================================= template parameter 2 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v2_available = (template_v2 != null)
##======================================= template parameter 2 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if template_available && template_last_label != null
##======================================= template finished
{{{, }}}}}}{{{#!if template_last_label != null
{{{...}}}}}}{{{#!if template_available || template_last_label != null
{{{>}}}}}}{{{#!if do_template_linebreak = (template_lnb != null)
##======================================= template linebreak
[br]}}}{{{#!if do_template_linebreak ;nbsp
}}}
#!if head_keyword_available!=null
{{{ }}}
#!if fn_attribute!=null
##======================================= contents
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr1]]}}}}}}]] 
#!if fn_attribute_lnk!=null
[[C++/문법/특성#attr1|{{{#a8a8a8 {{{[[attr1]]}}}}}}]] 
#!if fn_attribute_lnb!=null
[br]
#!if fn_attribute_lnb!=null ;nbsp
#!if kw1!=null
'''{{{#569cd6,#CornFlowerBlue {{{contexpr}}}}}}'''{{{#!if kw1_post!=null
{{{_kwp1}}}}}}{{{#!if kw1_post==null
{{{ }}}}}}
#!if kw2!=null
'''{{{#569cd6,#CornFlowerBlue {{{long long}}}}}}'''{{{#!if kw2post!=null
{{{&&}}}}}}{{{#!if kw2_post==null
{{{ }}}}}}
#!if cls_attribute!=null
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr2]]}}}}}}]]{{{ }}}
#!if cls_attribute_lnk!=null
[[C++/문법/특성#attr2|{{{#a8a8a8 {{{[[attr2]] }}}}}}]]
#!if ns1!=null
##======================================= namespaces
'''{{{#58fafe {{{std}}}}}}'''
#!if ns2!=null
{{{::}}}'''{{{#58fafe {{{chrono}}}}}}'''
#!if ns1!=null
{{{::}}}
#!if pre1_t!=null
##======================================= types at the front
{{{#4ec9b0,#6fdbba {{{system_clock }}}}}}
#!if pre2_t!=null
{{{::}}}{{{#4ec9b0,#6fdbba {{{duration }}}}}}
#!if pre_e!=null
{{{::}}}{{{#f0f068 {{{enum }}}}}}
#!if pre_post!=null
{{{_pre}}}
#!if pre_lnb!=null
[br] 
#!if do_pre_linebreak = (pre_lnb != null) &nbsp;
##======================================= pre-body things finished
#!if (!do_pre_linebreak && !do_template_linebreak && head_keyword_available) && (body_v != null || body_f != null || body_mv != null || body_mf != null || body_post != null)
{{{ }}}
#!if body_v!=null
##======================================= identifiers of variable and function
{{{#a8a8a8,#a1a1a1 {{{val}}}}}}
#!if body_mv!=null
{{{#ffffff {{{mem_val}}}}}}
#!if body_f!=null
{{{#f87a7a {{{fun}}}}}}
#!if body_mf!=null
{{{#f0a962 {{{mem_fn}}}}}}
#!if body_post!=null
##======================================= argument 1
{{{}}}
#!if body_tmpopen!=null
{{{<}}}
#!if body_bopen!=null
{{{(}}}
#!if arg1_concept!=null
'''{{{#4ec9b0,#6fdbba {{{concept1}}}}}}'''{{{#!if arg1_concept_params!=null
{{{<}}}{{{#!if arg1_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg1_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg1_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg1_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg1_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg1_t!=null
{{{#4ec9b0,#6fdbba {{{type1}}}}}}
#!if arg1_t_post!=null
{{{&}}}
#!if arg1_param != null && (arg1_kw != null || arg1_t_kw != null || arg1_t != null)
{{{ }}}
#!if arg1_param!=null
{{{#bcdce6 {{{param1}}}}}}
#!if arg2_concept != null || arg2_kw != null || arg2_t_kw != null || arg2_t != null || arg2_param != null
{{{, }}}
#!if arg2_concept!=null
##======================================= argument 2
'''{{{#4ec9b0,#6fdbba {{{concept2}}}}}}'''{{{#!if arg2_concept_params!=null
{{{<}}}{{{#!if arg2_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg2_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg2_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg2_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg2_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg2_t!=null
{{{#4ec9b0,#6fdbba {{{type2}}}}}}
#!if arg2_t_post!=null
{{{&}}}
#!if arg2_param!=null && (arg2_kw != null || arg2_t_kw != null || arg2_t != null)
{{{ }}}
#!if arg2_param!=null
{{{#bcdce6 {{{param2}}}}}}
#!if arg3_concept != null || arg3_kw != null || arg3_t_kw != null || arg3_t != null || arg3_param != null
{{{, }}}
#!if arg3_concept!=null
##======================================= argument 3
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg3_concept_params!=null
{{{<}}}{{{#!if arg3_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg3_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg3_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg3_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg3_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg3_t!=null
{{{#4ec9b0,#6fdbba {{{type3}}}}}}
#!if arg3_t_post!=null
{{{&}}}
#!if arg3_param!=null && (arg3_kw != null || arg3_t_kw != null || arg3_t != null)
{{{ }}}
#!if arg3_param!=null
{{{#bcdce6 {{{param3}}}}}}
#!if arg4_concept != null || arg4_kw != null || arg4_t_kw != null || arg4_t != null || arg4_param != null
{{{, }}}
#!if arg4_concept!=null
##======================================= argument4
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg4_concept_params!=null
{{{<}}}{{{#!if arg4_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg4_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg4_concept_tparam4!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg4_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg4_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg4_t!=null
{{{#4ec9b0,#6fdbba {{{type4}}}}}}
#!if arg4_t_post!=null
{{{&}}}
#!if arg4_param!=null && (arg4_kw != null || arg4_t_kw != null || arg4_t != null)
{{{ }}}
#!if arg4_param!=null
{{{#bcdce6 {{{param4}}}}}}
#!if arg5_param!=null
##======================================= argument5, argument6
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg6_param != null
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg_last_dots != null
{{{, ...}}}
#!if body_bclose!=null
##=======================================
{{{)}}}
#!if body_lnb!=null
[br] 
#!if body_lnb!=null
{{{ }}}
#!if body_spec1!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{ const}}}}}}'''
#!if body_spec1_paren != null
{{{(}}}
#!if body_spec1_ref!=null
{{{&}}}
#!if body_spec2!=null
{{{#!if body_spec1!=null && body_spec1_paren == null
{{{ }}}}}}'''{{{#DodgerBlue,#CornFlowerBlue {{{noexcept}}}}}}'''
#!if body_spec2_paren != null
{{{(}}}
#!if body_spec2_label != null
{{{...}}}
#!if body_spec2_paren != null
{{{)}}}
#!if body_spec1_paren != null
{{{)}}}
#!if body_tmpclose!=null
##======================================= last labels
{{{>}}}
#!if label_last!=null
{{{}}}
#!if last!=null
{{{;}}}


템플릿 매개변수 (Template Parameter)
템플릿의 구현은 템플릿 매개변수를 정의하는 것부터 시작한다. C++ 코드 상에서는 template 예약어를 맨 앞에 붙이고, 꺽쇠 괄호 <> 안에 템플릿 매개변수를 작성하는 식으로 구현한다. 템플릿 매개변수는 자료형과 컴파일 시점 상수를 넣을 수 있다.

2.1. 자료형 템플릿 매개변수

[include(틀:C++ 요소, head_keyword=template,
template_p0=자료형-템플릿-매개변수1, template_p1=자료형-템플릿-매개변수2,
template_last_label=..., last=;)]
자료형 템플릿 매개변수 (Type Template Parameter)
template 매개변수에 자료형을 넣을 수 있다. 예약어 typename 혹은 class와 자료형 이름을 기입하면 된다. typename은 다른 언어에서 볼 수 있는 type과도 같다. 목적과 기능은 거의 같지만 차이점은 C++에선 템플릿에서만 사용할 수 있다는 점이다.

2.2. 상수 템플릿 매개변수

상수 템플릿 매개변수 (Constant Template Parameter)
[include(틀:C++ 요소, head_keyword=template,
template_v0=상수-템플릿-매개변수1, template_v0_ty=템플릿-매개변수1-자료형,
template_v1=상수-템플릿-매개변수2, template_v1_ty=템플릿-매개변수1-자료형,
template_last_label=..., last=;)]
C++의 템플릿 매개변수에는 자료형이 아닌 값이 올 수 있다. 그러나 컴파일 시점에 정해지는 상수(Constant)여야만 한다. 다시 말하지만 함수형 프로그래밍 언어에서의 상수다. 상수 매개변수의 순서는 자료형 매개변수와는 상관없다.

부동 소수점 (float, double)을 지원하는지 아닌지는 컴파일러에 따라 다르다. 표준에서는 bool과 정수만 쓰였다.

2.3. 중첩 템플릿 매개변수

템플릿의 템플릿 매개변수 (Template Template Parameter)
놀랍게도 템플릿 매개변수는 또다른 템플릿이 될 수 있다. 이를 활용하는 방법은 메타 프로그래밍 문서에서 소개한다.

3. 함수 템플릿

함수 템플릿 (Function Template)
함수 템플릿은 명시한 템플릿 내용에 따라 함수를 실체화(Instantiation)하는 기능이다. C++에서 일반화 프로그래밍을 하기 위한 필수요소로써, 사용자가 함수에 어떤 값이든 인자로 전달할 수 있게 해준다. 템플릿 자료형 매개변수를 함수의 매개변수의 자료형으로 사용하거나, 혹은 반환 자료형으로 사용할 수 있다. 코드에서는 함수의 재사용성을 크게 늘려주고 구현해야 할 코드의 양을 기하급수적으로 줄이는 효과가 있다. 또한 범용적으로 쓰일 수 있는 함수를 만듦으로써 구현 여하에 따라 유지보수의 필요성을 줄여줄 수도 있다.

다른 언어에서도 제네릭(일반화된) 함수라 하여 C++과 마찬가지의 역할을 하는 기능이 크게 보편화 되어있다. 함수 템플릿은 보통의 어플리케이션 개발자라도 자주 구현하고 자주 쓸 기능이므로 만약 맛보기로 배우는 것도 나쁘지 않다. 난이도도 어려운 것이 없고 말이다.
#!syntax cpp
template<typename T>
T Add(T lhs, T rhs)
{
    return lhs + rhs;
}
먼저 간단한 함수를 생각해보자. 이 함수 `Add`는 같은 자료형의 매개변수 두개를 받아 더하고 반환하는 함수다.
#!syntax cpp
int main()
{
    // (1) 템플릿 자료형 매개변수 `T`는 int
    // `result0`는 int
    // `result0`의 값은 80
    auto result0 = Add(50, 30);

    // (2) 템플릿 자료형 매개변수 `T`는 std::string
    // `result1`은 std::string
    // `result1`의 값은 "LeftRight"
    std::string right{ "Right" };
    auto result1 = Add(std::string{ "Left" }, right);
}
상기 코드는 같은 자료형을 대입한 예제다. 원시 자료형부터, +연산자오버로딩된 클래스까지 모두 인자로 넣을 수 있다.
#!syntax cpp
int main()
{
    // 컴파일 오류! 인수 목록이 일치하는 함수 "Add"의 인스턴스가 없습니다.
    auto result0 = Add(35.0f, 20UL);
    auto result1 = Add(60.0, std::complex{ 0, 3 });
}
상기 코드는 서로 다른 자료형을 대입한 예제다. 그러나 `T`에 대하여 공통되는 자료형을 알 수 없으므로 컴파일이 실패한다. 이 경우 템플릿 매개변수를 분리해야 한다.
#!syntax cpp
template<typename T, typename U>
T Add(T lhs, U rhs)
{
    return lhs + rhs;
}

int main()
{
    // (1) 템플릿 자료형 매개변수 `T`는 float
    // `result0`는 float
    // `result0`의 값은 55.0f
    auto result0 = Add(35.0f, 20UL);

    // (2) 컴파일 오류! `std::complex<double>`에서 `double`로의 적절한 변환 함수가 없습니다.
    auto result1 = Add(60.0, std::complex{ 0, 3 });
    // 이 경우 `T`가 std::complex가 되어 컴파일이 성공함.
    auto result1 = Add(std::complex{ 0, 3 }, 60.0);
}
첫번째 사례는 해결되었다. 하지만 두번째 사례에서는 여전히 오류가 발생한다. 이 경우 반환 자료형과 첫번째 템플릿 매개변수 `T`double인데, 이를 처리하기 위해서는 std::complex<double>double로 변환될 수 있어야 한다. 하지만 변환 연산자가 없으므로 컴파일 오류가 발생한다.
#!syntax cpp
template<typename T, typename U>
constexpr auto Add(T lhs, U rhs)
{
    return lhs + rhs;
}
이럴 땐 간단하게 auto를 쓰면 해결된다. TU를 더한 자료형을 즉시 추론해낸다. 여기까지가 제네릭을 캐주얼하게 활용하고자 하면 익혀야하는 단계다. 복잡하게 보일 순 있으나 다른 언어도 문법만 조금 다를 뿐이다. 가령 template<typename...> 대신 Function<T, U>처럼 쓰는 정도이며 똑같은 목적으로 똑같은 기능을 구현하는 것이다.

#!syntax cpp
template<typename T0, typename T1, typename T2>
void PrintLnb(const T0& v0, const T1& v1, const T2& v2)
{
    std::println("First: {}, Second: {}, Third: {}");
}

int main()
{
    // (1) PrintLnb(const int&, const int&, const float&)
    // "First: 1, Second: 2, Third: 3.0" 출력
    PrintLnb(1, 2, 3.0f);

    // (2) PrintLnb(const long long&, const double&, const (char&)[9])
    // "First: 99012884790171023, Second: 30.156, Third: Last One" 출력
    PrintLnb(99012884790171023, 30.156, "Last One");
}
새삼스럽지만 지금까지 썼던 std::print, std::println도 함수 템플릿이다. 상기 코드는 세개의 다른 템플릿 자료형 매개변수를 상수 참조자로 받아 출력하는 예제다.

3.1. 추가 설명

#!syntax cpp
template<typename T, typename U>
constexpr auto Add(T lhs, U rhs)
{
    return lhs + rhs;
}

// 상동
template<typename T, typename U>
constexpr auto Add(T lhs, U rhs) -> decltype(lhs + rhs)
{
    return lhs + rhs;
}

// 상동
template<typename T, typename U>
constexpr std::common_type_t<T, U> Add(T lhs, U rhs)
{
    return lhs + rhs;
}

// 상동
template<typename T, typename U>
constexpr auto Add(T lhs, U rhs) -> std::common_type_t<T, U>
{
    return lhs + rhs;
}

int main()
{
    // (1) 템플릿 자료형 매개변수 `T`는 float
    // `result0`는 float
    // `result0`의 값은 55.0f
    // 서로 다른 원시 자료형의 경우 float과 unsigned long의 공동 자료형으로 승격함.
    // 이 경우엔 float이 더 넓은 범위라 float으로 승격함.
    auto result0 = Add(35.0f, 20UL);

    // (2) 템플릿 자료형 매개변수 `T`는 std::complex<double>
    // `result1`는 std::complex<double>
    // `result1`의 값은 60.0 + 3i
    // `std::complex`는 double을 받는 생성자를 갖고 있음.
    // 이 경우엔 `std::complex`가 더 넓은 범위라 `std::complex`으로 승격함.
    auto result1 = Add(60.0, std::complex{ 0, 3 });
}
그러나 여기서 끝이 아니다. 배경지식을 더 알아보도록 하자. 조금 복잡하지만 하나하나 살펴보자.
#!syntax cpp
float result0 = Add(float{ 35.0f }, static_cast<float>(20UL));
첫번째 예시의 경우 Add(float, unsigned long);으로 다른 자료형이 대입되었다. 그러나 이 경우에 컴파일 오류는 일어나지 않는다. 만약 두 인자가 서로 static_cast를 통해 변환이 가능하다면 공동 자료형(Common Type)으로 변환된다. 이때 공동 자료형이라는 것은 원시 자료형 끼리라면 부동 소수점이 우선하고, 그 다음으로 바이트 크기를 우선한다. 가령 intdoubledouble로 변환된다. floatlong long이라면 float으로 변환된다. 이 변환은 암시적으로 일어나므로 정수 값이 잘려나가거나 부호가 날아갈 수 있다. 만약 이를 감지하는 컴파일러 옵션이 있다면 키는 것이 좋다.
#!syntax cpp
std::complex<double> result1 = Add(std::complex<double>{ 60.0 }, std::complex{ 0.0, 3.0 });
두번째 예시의 경우 클래스가 생성자에서 다른 자료형을 받는 생성자를 갖고 있어서 문제가 없다. 표준 라이브러리<complex> 모듈은 템플릿 복소수 클래스 std::complex<T>를 지원한다. 이 클래스는 double 두개로 실수 자리와 복소수 자리를 받는 생성자를 가지고 있다. 생성자의 복소수 매개변수의 기본값은 =0.0이라서 값을 전달하지 않아도 된다. 그래서 공동 자료형은 std::complex<double>이 된다.

3.2. 함수 매개변수의 기본값

#!syntax cpp
template<typename T0, typename T1>
auto PrintMultiply(T0 lhs, T1 rhs = 1)
{
    auto result = lhs * rhs;
    std::println("`{}` * `{}` is `{}`", lhs, rhs, result);

    return result;
}

int main()
{
    // (1) PrintMultiply(long, [int=1]);
    // `result0`는 long
    // `result0`의 값은 40L
    auto result0 = PrintMultiply(40L);

    // (2) PrintMultiply(double, [int=1]);
    // `result1`는 double
    // `result1`의 값은 70.0
    auto result1 = PrintMultiply(70.0);
}
함수 템플릿도 함수 매개변수의 기본값을 정해줄 수 있다. 만약 템플릿 자료형 매개변수를 쓴 함수 매개변수에 기본값을 정해줬다면, 해당 템플릿 자료형 매개변수가 기본값의 자료형으로 대입된다. 상기 코드에서는 함수 `PrintMultiply`의 두번째 매개변수에 템플릿 자료형 매개변수 `T1`가 쓰였다. 그리고 기본값으로 1이 주어졌는데, 인자가 전달되지 않으면 `T1``int`로 컴파일된다.

3.3. 함수 인자 연역

#!syntax cpp
template<typename T>
void Function(T param);

int main()
{
    // (1) 인자 `int&&`가 함수 매개변수에서 `int`로 부패함.
    Function(0);

    // (2) 인자 `float&`이 함수 매개변수에서 `float`으로 부패함.
    float v0 = 81.0f;
    Function(v0);

    // (3) 인자 `const short&`가 함수 매개변수에서 `short`로 부패함.
    const short v1 = 256;
    Function(v1);

    struct Squirrel {};

    // (4) 인자 `Squirrel&&`가 함수 매개변수에서 `Squirrel`로 부패함.
    Squirrel v2{};
    Function(std::move(v2));
}
유의할 점은 함수 템플릿의 함수 매개변수에도 auto의 사례처럼 부패 현상이 일어난다는 것이다. 때문에 참조자를 붙이는 등 별도의 조치가 없으면 자료형 한정자나 참조자가 사라진다.

3.4. 강제 인라인

마지막으로 템플릿으로 만든 함수의 명세를 언급하고 넘어가자. 템플릿으로 만든 함수는 반드시 inline이어야 한다. 즉 선언만 할 수 없으며 정의도 해줘야 한다. 다만 같은 소스 파일 안에서 선언을 먼저 해놓고 나중에 정의를 하는 건 문제가 없다. 이 특징 때문에 컴파일 시간이 길어지는 단점이 있다. 일반 함수라면 선언과 정의를 분리해서 정의가 있는 소스 파일을 따로 컴파일 할 수 있겠지만, 템플릿은 아니므로 삽입된 헤더나 모듈 마다 컴파일 시간을 잡아먹게 된다.

함수 템플릿은 무조건 상수 표현식은 아닌 것도 특기할만 하다. 템플릿 매개변수는 무조건 컴파일 시점에 정해지지만, 함수의 구현이나 매개변수의 값이 컴파일 시점에 정해진다는 보장은 없기 때문이다.

4. 변수 템플릿

변수 템플릿 (Variable Template)C++14
C++14부터 템플릿 필드를 정의할 수 있게 되었다. 템플릿 필드는 변수도 될 수 있고, 상수도 될 수 있고, 컴파일 시점 상수도 될 수 있다. 표준 라이브러리<type_traits> 모듈에서 자료형의 정보를 읽을 수 있는 갖가지 상수 템플릿을 제공하고 있다.
#!syntax cpp
import <type_traits>;

struct GameEntity
{};

template<typename T>
bool IsGameInstance = std::is_base_of_v<GameEntity, T>;

struct GameManager {};
struct Player : GameEntity {};
struct EnemyParent : GameEntity {};
struct EnemyFlyer0 : EnemyParent {};

// (1) `is_GameManager_GameEntity`는 false
bool is_GameManager_GameEntity = IsGameInstance<GameManager>;

// (2) `is_Player_GameEntity`는 true
bool is_Player_GameEntity = IsGameInstance<Player>;

// (3) `is_EnemyParent_GameEntity`는 true
bool is_EnemyParent_GameEntity = IsGameInstance<EnemyParent>;

// (4) `is_EnemyFlyer0_GameEntity`는 true
bool is_EnemyFlyer0_GameEntity = IsGameInstance<EnemyFlyer0>;
상기 코드는 표준 라이브러리의 std::is_base_of_v<Base, Derived>를 사용한 예제다. std::is_base_of_v<Base, Derived>는 클래스 Derived가 클래스 Base를 상속하는지 여부를 반환한다. std::is_base_of_v 역시 구조체 std::is_base_of에서 bool 값을 읽어오는 상수 템플릿이다.

한편 표준 라이브러리에서 웬만한 실사용례를 커버하고 있기 때문에 표준 명세를 살펴보는 게 좋다.

5. 기본 템플릿 인자값

기본 템플릿 인자값 (Default Template Arguments)
함수 매개변수 뿐만 아니라, 템플릿 매개변수에도 기본 값(기본 자료형)을 전달할 수 있다. 지금까지 함수 매개변수에 기본 값을 넣었던 것 처럼 = 뒤에 기본으로 들어갈 자료형의 이름 또는 상수값을 넣으면 된다. 당연히 변수 템플릿에도 사용할 수 있다. 하지만 기본값이 주어진 템플릿 매개변수는 뒤쪽에 배치해야 한다. 함수 매개변수에 기본값을 썼던 사례와 같다.
#!syntax cpp
template<typename T, typename U = int>
[[nodiscard]]
constexpr U ConvertToInteger(const T& value) noexcept
{
    return static_cast<U>(value);
}

int main()
{
    // (1) `v0`의 값은 50
    auto v0 = ConvertToInteger(50.5f);

    // (2) `v1`의 값은 8080L
    auto v1 = ConvertToInteger<double, long>(8080.8080);
}
상기 코드는 정수로 변환하는 함수 템플릿을 보여주고 있다. 두번째 템플릿 자료형 매개변수 `U`에 기본 자료형 int가 대입되어 만약 별도로 원하는 자료형이 없으면 `U`int로 치환된다. 하지만 두번째 예시에서 볼 수 있듯이 정작 쓰기에는 조금 불편하다. 그럴 때는,
#!syntax cpp
template<typename U = int>
[[nodiscard]]
constexpr U ConvertToInteger(const auto& value) noexcept
{
    return static_cast<U>(value);
}

int main()
{
    // `val`의 값은 8080L
    auto val = ConvertToInteger<long>(8080.8080);
}
auto를 쓰면 깔끔하게 해결된다.

6. 명시적 템플릿 매개변수

명시적 템플릿 매개변수 (Explicit Template Arguments)
변수 템플릿과 함수 템플릿을 사용할 때 템플릿 매개변수를 직접 명시할 수 있다. 부패 혹은 잘못된 형변환 따위로 값이 소실되는 걸 막을 수 있다.
#!syntax cpp
template<typename T>
[[nodiscard]]
constexpr T GetDefaultValue() noexcept(std::is_nothrow_default_constructible<T>)
{
    return {};
}

enum class EmptyEnumeration {};

struct NotDefaultInitializable
{
    constexpr NotDefaultInitializable() = delete;
};

int main()
{
    // (1) `v0`의 값은 0.0f
    // `T`는 float
    // 템플릿 명세는 `GetDefaultValue<float>`
    // 함수 서명은 `constexpr float GetDefaultValue() noexcept(true)`
    // 함수 템플릿 인스턴스는 `float GetDefaultValue<float>() noexcept`
    const auto v0 = GetDefaultValue<float>();

    // (2) `v1`의 값은 EmptyEnumeration{ 0 }
    // `T`는 EmptyEnumeration
    // 템플릿 명세는 `GetDefaultValue<EmptyEnumeration>`
    // 함수 서명은 `constexpr EmptyEnumeration GetDefaultValue() noexcept(true)`
    // 함수 템플릿 인스턴스는 `EmptyEnumeration GetDefaultValue<EmptyEnumeration>() noexcept`
    const auto v1 = GetDefaultValue<EmptyEnumeration>();

    // (3) `v2`의 값은 std::string{}
    // `T`는 std::string
    // 템플릿 명세는 `GetDefaultValue<std::string>`
    // 함수 서명은 `constexpr std::string GetDefaultValue() noexcept(false)`
    // 함수 템플릿 인스턴스는 `std::string GetDefaultValue<std::string>()`
    const auto v2 = GetDefaultValue<std::string>();

    // (4) 컴파일 오류! 생성자가 삭제되었습니다.
    const auto v3 = GetDefaultValue<NotDefaultInitializable>();
}
상기 코드는 기본값을 써서 초기화하는 예시를 보여주고 있다. `GetDefaultValue`는 템플릿 매개변수 `T`의 기본 초기화를 수행한 값을 반환하는 함수다. 원시 자료형, 열거형, 기본 생성자가 있는 클래스는 모두 실행시킬 수 있다. 그러나 기본 생성자가 없는 클래스에 대해서는 실행할 수 없다.

7. 템플릿 매개변수 순서

템플릿 매개변수의 순서는 유동적으로 정할 수 있다. 순서를 코드에서 쓰인 순서와는 상관없이 무작위로 배치할 수도 있다. 앞서 템플릿 매개변수에 기본값을 넣으면 맨 뒤에 놓아야 한다고 했었다. 그런데 함수 템플릿의 경우 예외가 있다.
#!syntax cpp
template<typename U = int, typename T>
[[nodiscard]]
constexpr U ConvertToInteger(const T& value) noexcept
{
    return static_cast<U>(value);
}
함수의 매개변수에 사용된 템플릿 매개변수는 그야말로 아무데나 놓아도 상관없다. 왜냐하면 그 템플릿 매개변수는 템플릿 명세에 귀속된 게 아니라, 함수 서명에 귀속되기 때문이다. 앞서 봤던 예제의 정수 변환 함수는 편의상 auto를 쓰긴 하지만 사실 함수 매개변수의 자료형을 뒤쪽에 놓을 수 있다.
#!syntax cpp
import <array>;

template<typename U = int, size_t L, typename T>
[[nodiscard]]
consteval bool CanBeInsertedTo(const std::array<T, L>& array, U = {}) noexcept
{
    return std::is_constructible_v<T, U>;
}

int main()
{
    // (0) 정적 배열
    // `CanBeInsertedTo`에 사용되었을 때 `T`는 int, `L`은 10
    constexpr std::array<int, 10> array{};

    // (1) `can0`의 값은 true
    // `U`는 int
    // 템플릿 명세는 `CanBeInsertedTo<int, int, 10ULL>`
    // 함수 서명은 `consteval bool (const std::array<int, 10>& array, [U={}]) noexcept`
    // 함수 템플릿 인스턴스는 `CanBeInsertedTo<int, int, 10ULL>(std::array<int, 10>& array, int)`
    const auto can0 = CanBeInsertedTo(array, 100);

    // (2) `can1`의 값은 true
    // `U`는 double
    // 템플릿 명세는 `CanBeInsertedTo<double, int, 10ULL>`
    // 함수 서명은 `consteval bool (const std::array<int, 10>& array, [U={}]) noexcept`
    // 함수 템플릿 인스턴스는 `CanBeInsertedTo<double, int, 10ULL>(std::array<int, 10>& array, double)`
    const auto can1 = CanBeInsertedTo<double>(array);

    // (3) `can2`의 값은 true
    // `U`는 unsigned long long
    // 인자 `float{ 20.90f }`이 unsigned long long으로 암시적 형변환됨.
    // 템플릿 명세는 `CanBeInsertedTo<unsigned long long, int, 10ULL>`
    // 함수 서명은 `consteval bool (const std::array<int, 10>& array, [U={}]) noexcept`
    // 함수 템플릿 인스턴스는 `CanBeInsertedTo<unsigned long long, int, 10ULL>(std::array<int, 10>& array, unsigned long long)`
    const auto can2 = CanBeInsertedTo<unsigned long long>(array, 20.90f);

    // (4) `can3`의 값은 false
    // `U`는 const char*
    // 템플릿 명세는 `CanBeInsertedTo<const char*, int, 10ULL>`
    // 함수 서명은 `consteval bool (const std::array<int, 10>& array, [U={}]) noexcept`
    // 함수 템플릿 인스턴스는 `CanBeInsertedTo<const char*, int, 10ULL>(std::array<int, 10>& array, const char*)`
    const auto can3 = CanBeInsertedTo(array, "");

    // (5) `can4`의 값은 false
    // `U`는 std::string
    // 템플릿 명세는 `CanBeInsertedTo<std::string, int, 10ULL>`
    // 함수 서명은 `consteval bool (const std::array<int, 10>& array, [U={}]) noexcept`
    // 함수 템플릿 인스턴스는 `CanBeInsertedTo<std::string, int, 10ULL>(std::array<int, 10>& array, std::string)`
    const auto can4 = CanBeInsertedTo<std::string>(array);

    // (6) `can5`의 값은 false
    // `U`는 std::string
    // 템플릿 명세는 `CanBeInsertedTo<std::string, int, 10ULL>`
    // 함수 서명은 `consteval bool (const std::array<int, 10>& array, [U={}]) noexcept`
    // 함수 템플릿 인스턴스는 `CanBeInsertedTo<std::string, int, 10ULL>(std::array<int, 10>& array, std::string)`
    const auto can5 = CanBeInsertedTo<std::string>(array, "");
}
상기 코드는 기본 템플릿 매개변수, 기본 함수 매개변수를 조합한 예시를 보여주고 있다. 예제의 함수 `CanBeInsertedTo`는 어떤 배열에 원소 `U`를 넣을 수 있는지 없는지를 판별하는 함수다. 함수 매개변수에 기본값이 주어지면 반드시 맨 뒤에 배치해야 한다. 템플릿 매개변수가 함수 매개변수에 의존적이면 배치 순서는 상관없다. 함수 매개변수의 기본값은 템플릿 자료형 매개변수로 형변환된다.

8. 템플릿 인자 연역

템플릿 인자 연역 (Template Argument Deduction)
<C++ 예제 보기>
#!syntax cpp
import <string>;
import <print>;

template<typename T>
void increment1(T x)
{
    ++x;
}

void increment2(auto x)
{
    ++x;
}

template<typename T>
void increment3(T lhs, T rhs)
{
    lhs += rhs;
}

template<typename T>
void increment4(T lhs, const T* rhs)
{
    lhs += *rhs;
}

void increment5(auto lhs, auto rhs)
{
    lhs += rhs;
}

int main()
{
    int a = 100;
    long long b = 500;
    const int c = 900;
    int& d = a;

    increment1(a); // (1) 아무것도 안 함
    increment1(510942633); // (2)
    increment1(b); // (3)
    increment1(d); // (4) d는 a의 참조 변수이지만 &가 부패해서 사라진다.

    increment1('B'); // (5)
    increment1("namu"); // 오류! 문자열은 더할 수 없습니다.

    increment2(a); // (4)
    increment2(a + b); // (5) 값에 의한 전달은 prvalue도 전달할 수 있다.
    increment2('B'); // (6)
    increment2("wiki"); // 오류! 문자열은 더할 수 없습니다.

    increment3(a, 1058142); // (7)
    increment3(a, c); // (8) 인자의 const는 매개변수의 auto에 영향을 끼치지 못한다.
    increment3(a, b); // 오류! 전달된 두 매개변수 T의 자료형이 서로 다릅니다.

    increment4(a, &c); // (9) 포인터(주소)는 glvalue만이 가질 수 있다. glvalue는 lvalue라서 모든 한정자를 반드시 유지한다.
    increment4(a, &b); // 오류! 전달된 두 매개변수 T의 자료형이 서로 다릅니다.
    increment4(a, &d); // 오류! 포인터와 인자의 const 한정자가 일치하지 않습니다.

    increment5(a, b); // (10)
    increment5(d, c); // (11) d는 참조형이지만 auto에서 &가 부패해서 사라진다.
    increment5(c, d); // (12)
    increment5(d, b); // (13)
    increment5(std::string{ "Namu" }, std::string{ "Wiki" }); // (14)

    std::println("a의 값: {}", a) // 100
    std::println("b의 값: {}", b) // 500
    std::println("c의 값: {}", c) // 900
    std::println("d의 값: {}", d) // 100 (a의 참조형)

    return 0;
}
상기 코드는 부패가 일어나는 사례를 보여주고 있다. 여기서 예제의 increment1 함수와 increment2 함수가 서로 같은 동작을 함을 알 수 있다. 이게 중요한 이유는 사실 템플릿과 auto가 서로 같은 존재임을 보여주기 때문이다.

auto는 편하게 코드를 쓰라고 추가된 기능이긴 하지만 갑자기 하늘에서 뚝 떨어진 기능이 아니다. 과거 호환성에 집착할 수 밖에 없는 C++ 환경에 기존 코드 베이스를 파괴할 수 있는 기능이 추가될리도 없고 말이다. auto는 바로 각각 다른 자료형으로 추론되는 템플릿을 간편하게 쓸 수 있게 해주는 문법적 설탕이다. auto를 사용한 필드는 변수 템플릿이며 auto가 사용된 함수는 함수 템플릿이다. 그렇기에 auto나 템플릿이나 원래의 한정자가 부패하는 것이다.

<C++ 예제 보기>
#!syntax cpp
import <type_traits>;

void ValueFunction(auto value);
void LvalueFunction(auto& value);
void RvalueFunction(auto&& value);

int main()
{
    const long A = 132435;

    ValueFunction(A); // value는 long
    ValueFunction(std::move(A)); // value는 long
    ValueFunction(8000); // 리터럴 value는 int

    LvalueFunction(A); // value&는 const long&
    LvalueFunction(std::move(A)); // value&는 const long&
    LvalueFunction(8000); // 컴파일 오류! 리터럴은 lvalue에 대입할 수 없음

    RvalueFunction(A); // value&&는 const long&
    RvalueFunction(std::move(A)); // value&&는 const long&&
    RvalueFunction(8000); // 리터럴 value&&는 int&&
}
만약 사용자가 auto&를 쓰면 &에 의존하는 모든 한정자가 딸려나온다. 굳이 const volatile을 붙이지 않아도 말이다. 그러나 auto&는 무조건 lvalue가 되어서 &&로 표현되는 리터럴과 임시 객체를 넣을 수 없다. 예를 들어서 예제의 LvalueFunction에는 500, int(120648395)같은 값을 전달할 수 없다. 그럼 좌측값, 우측값 구분을 위해 const& T, T&&를 모두 오버로딩해야만 할까? 사실 그렇지 않다.

앞서 부패 현상을 막기 위한 방법을 소개했었다. 하지만 한정자와 참조자를 붙이는 작업은 불완전하고 실수를 유발하기 쉽다. 자료형 기입이 아무리 쉬워졌다고 해도 여전히 반복적이고 번거로운 작업이기 때문이다. 또한 decltype(auto)는 함수 매개변수에 사용하지 못한다. 거기에 템플릿에 어떤 값이 들어올지도 불확실한데 최적화까지 고려해야 한다. 결국 이러면 처음으로 돌아가 원래 자료형을 명시하는 게 더 이롭다. 다음 단락에서는 완벽한 해결방법을 소개한다. 온전한 자료형을 얻는 방법 말이다.

9. 완벽한 매개변수 전달

완벽한 전달 (Perfect Forwarding)
완벽한 매개변수 전달은 C++에서 인자의 자료형을 매개변수로 고스란히 전해줄 수 있는 기능이다. 우측값 참조자의 &&를 잠시 빌려온 전이 참조라는 새로운 개념이 등장한다. 전이 참조는 템플릿에서 자료형 추론이 일어나는 상황이면 인자를 부패시키지 않는다. 곧 인자가 T&& 혹은 auto&&인 함수 매개변수에 대입되었을 때 템플릿과 auto에서 한정자와 참조자를 유지해준다는 뜻이다.
#!syntax cpp
import <utility>;

// (1) lvalue, rvalue(xvalue & prvalue) 모두가 원래 값 범주(Value Category)를 유지한채, 아무 비용없이 전달된다.
template<typename T>
T&& ForwardingByTemplate(T&& value) noexcept
{
    // lvalue는 lvalue 그대로 전달된다.
    // xvalue를 감싸 이름없이 전달한다.
    // prvalue를 감싸 이름없이 전달한다.
    return std::forward<T>(value);
}

// (2) C++23부터 사용할 수 있는 ForwardingByTemplate과 같은 코드
auto&& ForwardingByDeduction(auto&& value) noexcept
{
    // C++23부터 가능한 완벽한 전달 수단
    return auto{ value };
    // 또는
    return auto(value);
}

int main()
{
    // (3) `result0`는 long&&
    decltype(auto) result0 = ForwardingByTemplate(20L);
    // 이 실행문은
    // long result0{ 20L };
    // 로 컴파일 된다.

    // (4) `result1`은 std::string&&
    decltype(auto) result1 = ForwardingByTemplate(std::string{ "Hello" });
    // 이 실행문은
    // std::string result1{ "Hello" };
    // 로 컴파일 된다.

    // (5) `result2`는 `target`을 가리키는 int&
    int target{};
    decltype(auto) result2 = ForwardingByTemplate(target);
    // 이 실행문은
    // int& result2 = target;
    // 으로 컴파일 된다.

    // (6) `result3`은 std::string
    // std::string이 이동 생성되므로 컴파일 결과의 차이는 없다.
    auto result3 = ForwardingByTemplate(std::string{ "World" });
    // 이 실행문은
    // std::string result3{ "World" };
    // 로 컴파일 된다.
}
상기 코드는 아무런 메모리와 성능에 영향 없이 인자를 전달하는 함수의 구현이다. 완벽한 전달의 특징은 복잡한 함수의 안팎을 드나드는데 그 어떤 방해나 복사와 예외없이 값을 전달한다는 것이다. `ForwardingByTemplate`std::forward로 함수를 두 번 거치지만 릴리스 모드에선 어떤 흔적도 없이 최적화된다. 매개변수가 뭔지, 복사해야 할지 참조해야 할지 이동시켜야 할지 고민할 필요를 없애준다. 덕분에 원본 자료형이 뭔지 알기 위해 일일이 decltype(auto)을 쓸 필요도 없고, 매개변수에도 사용할 수 있다.

<C++ 예제 보기>
#!syntax cpp
template<typename... Ts>
constexpr auto ForwardAsTuple(Ts&&... args) noexcept(std::conjunction_v<std::is_nothrow_constructible<Ts, Ts&&>...>)
    -> std::tuple<Ts&&...>
{
    // 완벽한 전달(Perfect Forwarding) 사용
    return std::tuple<Ts&&...>{ std::forward<Ts>(args)... };
}

int main()
{
    const long A = 0; // 0
    unsigned long long B = 93140583732; // 1
    bool C = false; // 2
    bool& D = C; // 3
    Squirrel E{}; // 4
    const Squirrel& F = E; // 5
    constexpr unsigned G = 34275860428; // 6

    // (1) 모든 한정자와 참조자가 부패함.
    auto tuple1 = std::make_tuple(A, B, C, D, E, F, G);

    // 복사본은 원본 C, D에 영향을 주지 못함
    std::get<2>(tuple1) = true;

    // (2) 완벽한 전달 수행함.
    auto tuple2 = ForwardAsTuple(A, std::move(B), C, D, E, F, Squirrel{}, std::move(G));

    // C, D가 true가 됨.
    std::get<2>(tuple2) = true;
    // E, F의 `myName`의 값이 "new name"이 됨.
    std::get<4>(tuple2).myName = "new name";

    return 0;
}
여기서 tuple1은 복사본 튜플이 되어 std::tuple<long, unsigned long long, bool, bool, Squirrel, Squirrel, unsigned>로 생성된다. 그러나 tuple2는 완벽한 전달을 수행하여 std::tuple<const long&, unsigned long long&&, bool&, bool&, Squirrel&, const Squirrel&, Squirrel&&, const unsigned&&>가 된다.

참고로 예제의 ForwardAsTuple과 같은 기능의 함수가 이미 표준 라이브러리에 std::forward_as_tuple로 구현되어 있으므로 굳이 또 구현할 필요는 없다.

C++23에서는 값 범주(Value Category)를 유지하고 자료형만 변환하는 함수 std::forward_like가 추가되었다.

10. 클래스와 멤버 템플릿

클래스 템플릿을 보기 이전에 템플릿을 쓴 멤버를 살펴보자.

10.1. 멤버 함수 템플릿

[include(틀:C++ 요소,
kw1=class,
cls_attribute=특성,
pre1_t=클래스-식별자)]
#!if head_comment!=null
{{{#57A64A,#5EBD46 }}}
#!if head_lnb!=null
##======================================= include and import
[br]
#!if import!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{import }}}}}}'''{{{#C8865E {{{<>}}}}}}{{{;}}}
#!if include!=null
##======================================= the keyword at head (can be `template`)
{{{#include <>}}}
#!if (head_keyword_available = head_keyword != null)
##======================================= template parameters begin
'''{{{#DodgerBlue,#CornFlowerBlue {{{}}}}}}'''
#!if template_available = (template_p0 != null || template_v0 != null || template_p1 != null || template_v1 != null || template_p2 != null || template_v2 != null || template_p3 != null || template_v3 != null)
#!if template_available || template_last_label != null
##======================================= template parameter 0
##======================================= template parameter 0 concept
{{{<}}}{{{#!if template_concept0_available = (template_cpt0 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept0_params_available = (template_concept0_p0 != null || template_concept0_p1 != null || template_concept0_p2 != null || template_concept0_p3 != null)
{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{<}}}}}}{{{#!if template_concept0_params_available
##======================================= template parameter 0 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p0 != null || template_concept0_v0 != null) && (template_concept0_p1 != null || template_concept0_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p1 != null || template_concept0_v1 != null) && (template_concept0_p2 != null || template_concept0_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p2 != null || template_concept0_v2 != null) && (template_concept0_p3 != null || template_concept0_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept0_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept0_available
{{{ }}}}}}{{{#!if template_p0_available = (template_p0 != null)
{{{#!if !template_concept0_available
##======================================= template parameter 0 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v0_available = (template_v0 != null)
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}
##======================================= template parameter 0 finished
}}}{{{#!if (template_p0_available || template_v0_available) && (template_p1 != null || template_v1 != null)
##======================================= template parameter 1
{{{, }}}}}}{{{#!if template_concept1_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept1_params_available = (template_concept1_p0 != null || template_concept1_p1 != null || template_concept1_p2 != null || template_concept1_p3 != null)
{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{<}}}}}}{{{#!if template_concept1_params_available
##======================================= template parameter 1 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p0 != null || template_concept1_v0 != null) && (template_concept1_p1 != null || template_concept1_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p1 != null || template_concept1_v1 != null) && (template_concept1_p2 != null || template_concept1_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p2 != null || template_concept1_v2 != null) && (template_concept1_p3 != null || template_concept1_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept1_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept1_available
{{{ }}}}}}{{{#!if template_p1_available = (template_p1 != null)
{{{#!if !template_concept1_available
##======================================= template parameter 1 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}}}}{{{#!if template_v1_available = (template_v1 != null)
##======================================= template parameter 1 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if (template_p1_available || template_v1_available) && (template_p2 != null || template_v2 != null)
##======================================= template parameter 2
{{{, }}}}}}{{{#!if template_concept2_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept2_params_available = (template_concept2_p0 != null || template_concept2_p1 != null || template_concept2_p2 != null || template_concept2_p3 != null)
{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{<}}}}}}{{{#!if template_concept2_params_available
##======================================= template parameter 2 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p0 != null || template_concept2_v0 != null) && (template_concept2_p1 != null || template_concept2_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p1 != null || template_concept2_v1 != null) && (template_concept2_p2 != null || template_concept2_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p2 != null || template_concept2_v2 != null) && (template_concept2_p3 != null || template_concept2_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept2_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept2_available
{{{ }}}}}}{{{#!if template_p2_available = (template_p2 != null)
{{{#!if !template_concept2_available
##======================================= template parameter 2 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v2_available = (template_v2 != null)
##======================================= template parameter 2 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if template_available && template_last_label != null
##======================================= template finished
{{{, }}}}}}{{{#!if template_last_label != null
{{{...}}}}}}{{{#!if template_available || template_last_label != null
{{{>}}}}}}{{{#!if do_template_linebreak = (template_lnb != null)
##======================================= template linebreak
[br]}}}{{{#!if do_template_linebreak ;nbsp
}}}
#!if head_keyword_available!=null
{{{ }}}
#!if fn_attribute!=null
##======================================= contents
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr1]]}}}}}}]] 
#!if fn_attribute_lnk!=null
[[C++/문법/특성#attr1|{{{#a8a8a8 {{{[[attr1]]}}}}}}]] 
#!if fn_attribute_lnb!=null
[br]
#!if fn_attribute_lnb!=null ;nbsp
#!if kw1!=null
'''{{{#569cd6,#CornFlowerBlue {{{{}}}}}}'''{{{#!if kw1_post!=null
{{{_kwp1}}}}}}{{{#!if kw1_post==null
{{{ }}}}}}
#!if kw2!=null
'''{{{#569cd6,#CornFlowerBlue {{{long long}}}}}}'''{{{#!if kw2post!=null
{{{&&}}}}}}{{{#!if kw2_post==null
{{{ }}}}}}
#!if cls_attribute!=null
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr2]]}}}}}}]]{{{ }}}
#!if cls_attribute_lnk!=null
[[C++/문법/특성#attr2|{{{#a8a8a8 {{{[[attr2]] }}}}}}]]
#!if ns1!=null
##======================================= namespaces
'''{{{#58fafe {{{std}}}}}}'''
#!if ns2!=null
{{{::}}}'''{{{#58fafe {{{chrono}}}}}}'''
#!if ns1!=null
{{{::}}}
#!if pre1_t!=null
##======================================= types at the front
{{{#4ec9b0,#6fdbba {{{system_clock }}}}}}
#!if pre2_t!=null
{{{::}}}{{{#4ec9b0,#6fdbba {{{duration }}}}}}
#!if pre_e!=null
{{{::}}}{{{#f0f068 {{{enum }}}}}}
#!if pre_post!=null
{{{_pre}}}
#!if pre_lnb!=null
[br] 
#!if do_pre_linebreak = (pre_lnb != null) &nbsp;
##======================================= pre-body things finished
#!if (!do_pre_linebreak && !do_template_linebreak && head_keyword_available) && (body_v != null || body_f != null || body_mv != null || body_mf != null || body_post != null)
{{{ }}}
#!if body_v!=null
##======================================= identifiers of variable and function
{{{#a8a8a8,#a1a1a1 {{{val}}}}}}
#!if body_mv!=null
{{{#ffffff {{{mem_val}}}}}}
#!if body_f!=null
{{{#f87a7a {{{fun}}}}}}
#!if body_mf!=null
{{{#f0a962 {{{mem_fn}}}}}}
#!if body_post!=null
##======================================= argument 1
{{{}}}
#!if body_tmpopen!=null
{{{<}}}
#!if body_bopen!=null
{{{(}}}
#!if arg1_concept!=null
'''{{{#4ec9b0,#6fdbba {{{concept1}}}}}}'''{{{#!if arg1_concept_params!=null
{{{<}}}{{{#!if arg1_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg1_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg1_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg1_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg1_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg1_t!=null
{{{#4ec9b0,#6fdbba {{{type1}}}}}}
#!if arg1_t_post!=null
{{{&}}}
#!if arg1_param != null && (arg1_kw != null || arg1_t_kw != null || arg1_t != null)
{{{ }}}
#!if arg1_param!=null
{{{#bcdce6 {{{param1}}}}}}
#!if arg2_concept != null || arg2_kw != null || arg2_t_kw != null || arg2_t != null || arg2_param != null
{{{, }}}
#!if arg2_concept!=null
##======================================= argument 2
'''{{{#4ec9b0,#6fdbba {{{concept2}}}}}}'''{{{#!if arg2_concept_params!=null
{{{<}}}{{{#!if arg2_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg2_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg2_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg2_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg2_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg2_t!=null
{{{#4ec9b0,#6fdbba {{{type2}}}}}}
#!if arg2_t_post!=null
{{{&}}}
#!if arg2_param!=null && (arg2_kw != null || arg2_t_kw != null || arg2_t != null)
{{{ }}}
#!if arg2_param!=null
{{{#bcdce6 {{{param2}}}}}}
#!if arg3_concept != null || arg3_kw != null || arg3_t_kw != null || arg3_t != null || arg3_param != null
{{{, }}}
#!if arg3_concept!=null
##======================================= argument 3
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg3_concept_params!=null
{{{<}}}{{{#!if arg3_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg3_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg3_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg3_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg3_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg3_t!=null
{{{#4ec9b0,#6fdbba {{{type3}}}}}}
#!if arg3_t_post!=null
{{{&}}}
#!if arg3_param!=null && (arg3_kw != null || arg3_t_kw != null || arg3_t != null)
{{{ }}}
#!if arg3_param!=null
{{{#bcdce6 {{{param3}}}}}}
#!if arg4_concept != null || arg4_kw != null || arg4_t_kw != null || arg4_t != null || arg4_param != null
{{{, }}}
#!if arg4_concept!=null
##======================================= argument4
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg4_concept_params!=null
{{{<}}}{{{#!if arg4_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg4_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg4_concept_tparam4!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg4_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg4_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg4_t!=null
{{{#4ec9b0,#6fdbba {{{type4}}}}}}
#!if arg4_t_post!=null
{{{&}}}
#!if arg4_param!=null && (arg4_kw != null || arg4_t_kw != null || arg4_t != null)
{{{ }}}
#!if arg4_param!=null
{{{#bcdce6 {{{param4}}}}}}
#!if arg5_param!=null
##======================================= argument5, argument6
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg6_param != null
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg_last_dots != null
{{{, ...}}}
#!if body_bclose!=null
##=======================================
{{{)}}}
#!if body_lnb!=null
[br] 
#!if body_lnb!=null
{{{ }}}
#!if body_spec1!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{ const}}}}}}'''
#!if body_spec1_paren != null
{{{(}}}
#!if body_spec1_ref!=null
{{{&}}}
#!if body_spec2!=null
{{{#!if body_spec1!=null && body_spec1_paren == null
{{{ }}}}}}'''{{{#DodgerBlue,#CornFlowerBlue {{{noexcept}}}}}}'''
#!if body_spec2_paren != null
{{{(}}}
#!if body_spec2_label != null
{{{...}}}
#!if body_spec2_paren != null
{{{)}}}
#!if body_spec1_paren != null
{{{)}}}
#!if body_tmpclose!=null
##======================================= last labels
{{{>}}}
#!if label_last!=null
{{{}}}
#!if last!=null
{{{;}}}


[include(틀:C++ 요소, head_keyword=template,
template_p0=템플릿-매개변수1, template_p1=템플릿-매개변수2,
template_last_label=..., template_lnb=1,
fn_attribute=특성,
pre1_t=반환-자료형, body_mf=멤버-함수-식별자,
arg1_t=매개변수1-자료형, arg1_param=매개변수1, arg2_t=매개변수2-자료형, arg2_param=매개변수2, arg_last_dots=1,
body_bopen=1, body_bclose=1, last=;)]
#!if head_comment!=null
{{{#57A64A,#5EBD46 }}}
#!if head_lnb!=null
##======================================= include and import
[br]
#!if import!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{import }}}}}}'''{{{#C8865E {{{<>}}}}}}{{{;}}}
#!if include!=null
##======================================= the keyword at head (can be `template`)
{{{#include <>}}}
#!if (head_keyword_available = head_keyword != null)
##======================================= template parameters begin
'''{{{#DodgerBlue,#CornFlowerBlue {{{}}}}}}'''
#!if template_available = (template_p0 != null || template_v0 != null || template_p1 != null || template_v1 != null || template_p2 != null || template_v2 != null || template_p3 != null || template_v3 != null)
#!if template_available || template_last_label != null
##======================================= template parameter 0
##======================================= template parameter 0 concept
{{{<}}}{{{#!if template_concept0_available = (template_cpt0 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept0_params_available = (template_concept0_p0 != null || template_concept0_p1 != null || template_concept0_p2 != null || template_concept0_p3 != null)
{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{<}}}}}}{{{#!if template_concept0_params_available
##======================================= template parameter 0 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p0 != null || template_concept0_v0 != null) && (template_concept0_p1 != null || template_concept0_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p1 != null || template_concept0_v1 != null) && (template_concept0_p2 != null || template_concept0_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p2 != null || template_concept0_v2 != null) && (template_concept0_p3 != null || template_concept0_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept0_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept0_available
{{{ }}}}}}{{{#!if template_p0_available = (template_p0 != null)
{{{#!if !template_concept0_available
##======================================= template parameter 0 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v0_available = (template_v0 != null)
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}
##======================================= template parameter 0 finished
}}}{{{#!if (template_p0_available || template_v0_available) && (template_p1 != null || template_v1 != null)
##======================================= template parameter 1
{{{, }}}}}}{{{#!if template_concept1_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept1_params_available = (template_concept1_p0 != null || template_concept1_p1 != null || template_concept1_p2 != null || template_concept1_p3 != null)
{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{<}}}}}}{{{#!if template_concept1_params_available
##======================================= template parameter 1 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p0 != null || template_concept1_v0 != null) && (template_concept1_p1 != null || template_concept1_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p1 != null || template_concept1_v1 != null) && (template_concept1_p2 != null || template_concept1_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p2 != null || template_concept1_v2 != null) && (template_concept1_p3 != null || template_concept1_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept1_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept1_available
{{{ }}}}}}{{{#!if template_p1_available = (template_p1 != null)
{{{#!if !template_concept1_available
##======================================= template parameter 1 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}}}}{{{#!if template_v1_available = (template_v1 != null)
##======================================= template parameter 1 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if (template_p1_available || template_v1_available) && (template_p2 != null || template_v2 != null)
##======================================= template parameter 2
{{{, }}}}}}{{{#!if template_concept2_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept2_params_available = (template_concept2_p0 != null || template_concept2_p1 != null || template_concept2_p2 != null || template_concept2_p3 != null)
{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{<}}}}}}{{{#!if template_concept2_params_available
##======================================= template parameter 2 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p0 != null || template_concept2_v0 != null) && (template_concept2_p1 != null || template_concept2_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p1 != null || template_concept2_v1 != null) && (template_concept2_p2 != null || template_concept2_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p2 != null || template_concept2_v2 != null) && (template_concept2_p3 != null || template_concept2_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept2_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept2_available
{{{ }}}}}}{{{#!if template_p2_available = (template_p2 != null)
{{{#!if !template_concept2_available
##======================================= template parameter 2 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v2_available = (template_v2 != null)
##======================================= template parameter 2 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if template_available && template_last_label != null
##======================================= template finished
{{{, }}}}}}{{{#!if template_last_label != null
{{{...}}}}}}{{{#!if template_available || template_last_label != null
{{{>}}}}}}{{{#!if do_template_linebreak = (template_lnb != null)
##======================================= template linebreak
[br]}}}{{{#!if do_template_linebreak ;nbsp
}}}
#!if head_keyword_available!=null
{{{ }}}
#!if fn_attribute!=null
##======================================= contents
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr1]]}}}}}}]] 
#!if fn_attribute_lnk!=null
[[C++/문법/특성#attr1|{{{#a8a8a8 {{{[[attr1]]}}}}}}]] 
#!if fn_attribute_lnb!=null
[br]
#!if fn_attribute_lnb!=null ;nbsp
#!if kw1!=null
'''{{{#569cd6,#CornFlowerBlue {{{}}}}}}}'''{{{#!if kw1_post!=null
{{{_kwp1}}}}}}{{{#!if kw1_post==null
{{{ }}}}}}
#!if kw2!=null
'''{{{#569cd6,#CornFlowerBlue {{{long long}}}}}}'''{{{#!if kw2post!=null
{{{&&}}}}}}{{{#!if kw2_post==null
{{{ }}}}}}
#!if cls_attribute!=null
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr2]]}}}}}}]]{{{ }}}
#!if cls_attribute_lnk!=null
[[C++/문법/특성#attr2|{{{#a8a8a8 {{{[[attr2]] }}}}}}]]
#!if ns1!=null
##======================================= namespaces
'''{{{#58fafe {{{std}}}}}}'''
#!if ns2!=null
{{{::}}}'''{{{#58fafe {{{chrono}}}}}}'''
#!if ns1!=null
{{{::}}}
#!if pre1_t!=null
##======================================= types at the front
{{{#4ec9b0,#6fdbba {{{system_clock }}}}}}
#!if pre2_t!=null
{{{::}}}{{{#4ec9b0,#6fdbba {{{duration }}}}}}
#!if pre_e!=null
{{{::}}}{{{#f0f068 {{{enum }}}}}}
#!if pre_post!=null
{{{_pre}}}
#!if pre_lnb!=null
[br] 
#!if do_pre_linebreak = (pre_lnb != null) &nbsp;
##======================================= pre-body things finished
#!if (!do_pre_linebreak && !do_template_linebreak && head_keyword_available) && (body_v != null || body_f != null || body_mv != null || body_mf != null || body_post != null)
{{{ }}}
#!if body_v!=null
##======================================= identifiers of variable and function
{{{#a8a8a8,#a1a1a1 {{{val}}}}}}
#!if body_mv!=null
{{{#ffffff {{{mem_val}}}}}}
#!if body_f!=null
{{{#f87a7a {{{fun}}}}}}
#!if body_mf!=null
{{{#f0a962 {{{mem_fn}}}}}}
#!if body_post!=null
##======================================= argument 1
{{{}}}
#!if body_tmpopen!=null
{{{<}}}
#!if body_bopen!=null
{{{(}}}
#!if arg1_concept!=null
'''{{{#4ec9b0,#6fdbba {{{concept1}}}}}}'''{{{#!if arg1_concept_params!=null
{{{<}}}{{{#!if arg1_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg1_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg1_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg1_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg1_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg1_t!=null
{{{#4ec9b0,#6fdbba {{{type1}}}}}}
#!if arg1_t_post!=null
{{{&}}}
#!if arg1_param != null && (arg1_kw != null || arg1_t_kw != null || arg1_t != null)
{{{ }}}
#!if arg1_param!=null
{{{#bcdce6 {{{param1}}}}}}
#!if arg2_concept != null || arg2_kw != null || arg2_t_kw != null || arg2_t != null || arg2_param != null
{{{, }}}
#!if arg2_concept!=null
##======================================= argument 2
'''{{{#4ec9b0,#6fdbba {{{concept2}}}}}}'''{{{#!if arg2_concept_params!=null
{{{<}}}{{{#!if arg2_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg2_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg2_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg2_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg2_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg2_t!=null
{{{#4ec9b0,#6fdbba {{{type2}}}}}}
#!if arg2_t_post!=null
{{{&}}}
#!if arg2_param!=null && (arg2_kw != null || arg2_t_kw != null || arg2_t != null)
{{{ }}}
#!if arg2_param!=null
{{{#bcdce6 {{{param2}}}}}}
#!if arg3_concept != null || arg3_kw != null || arg3_t_kw != null || arg3_t != null || arg3_param != null
{{{, }}}
#!if arg3_concept!=null
##======================================= argument 3
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg3_concept_params!=null
{{{<}}}{{{#!if arg3_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg3_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg3_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg3_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg3_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg3_t!=null
{{{#4ec9b0,#6fdbba {{{type3}}}}}}
#!if arg3_t_post!=null
{{{&}}}
#!if arg3_param!=null && (arg3_kw != null || arg3_t_kw != null || arg3_t != null)
{{{ }}}
#!if arg3_param!=null
{{{#bcdce6 {{{param3}}}}}}
#!if arg4_concept != null || arg4_kw != null || arg4_t_kw != null || arg4_t != null || arg4_param != null
{{{, }}}
#!if arg4_concept!=null
##======================================= argument4
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg4_concept_params!=null
{{{<}}}{{{#!if arg4_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg4_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg4_concept_tparam4!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg4_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg4_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg4_t!=null
{{{#4ec9b0,#6fdbba {{{type4}}}}}}
#!if arg4_t_post!=null
{{{&}}}
#!if arg4_param!=null && (arg4_kw != null || arg4_t_kw != null || arg4_t != null)
{{{ }}}
#!if arg4_param!=null
{{{#bcdce6 {{{param4}}}}}}
#!if arg5_param!=null
##======================================= argument5, argument6
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg6_param != null
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg_last_dots != null
{{{, ...}}}
#!if body_bclose!=null
##=======================================
{{{)}}}
#!if body_lnb!=null
[br] 
#!if body_lnb!=null
{{{ }}}
#!if body_spec1!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{ const}}}}}}'''
#!if body_spec1_paren != null
{{{(}}}
#!if body_spec1_ref!=null
{{{&}}}
#!if body_spec2!=null
{{{#!if body_spec1!=null && body_spec1_paren == null
{{{ }}}}}}'''{{{#DodgerBlue,#CornFlowerBlue {{{noexcept}}}}}}'''
#!if body_spec2_paren != null
{{{(}}}
#!if body_spec2_label != null
{{{...}}}
#!if body_spec2_paren != null
{{{)}}}
#!if body_spec1_paren != null
{{{)}}}
#!if body_tmpclose!=null
##======================================= last labels
{{{>}}}
#!if label_last!=null
{{{}}}
#!if last!=null
{{{;}}}


클래스의 멤버 함수도 함수 템플릿으로 만들 수 있다. 이 기능도 다른 언어에서 절찬리에 쓰이는 기능이다. 제네릭 메서드(일반화 메서드)라고 하여 클래스의 함수를 제네릭으로 만들어 쓰는 건 매우 유용하며 활용 사례도 풍부하다. auto와 함께 적극적으로 활용하면 좋다.

10.2. 데이터 멤버

데이터 멤버는 멤버 함수와는 달리 큰 제한이 존재한다. 정적이 아닌[3] 데이터 멤버 템플릿은 존재할 수 없다. 왜냐하면 템플릿 데이터 멤버가 있다면 컴파일 시점에, 실행 시점에도 자료형의 크기와 데이터 정렬 방식을 결정하지 못할 것이다. 그러면 메모리를 할당할 수 없고 객체를 생성할 수 없다. 즉 존재 자체가 불가능하다는 말이다.

자세한 예시를 들어보자. 만약 데이터 멤버가 템플릿이라면, 데이터 멤버 자체의 자료형도 템플릿에 의해 변하는 경우를 상상할 수 있다.
#!syntax cpp
struct IsntOk
{
    // 컴파일 오류!
    template<typename T>
    T stored_value {};
};

// 자료형의 크기를 알 수 없음.
size_t size = sizeof(IsntOk);
상기 코드는 데이터 멤버의 자료형이 템플릿에 의해 결정된다고 하면 나올 수 있는 상황을 보여주고 있다. 여기서 일단 알 수 있는 것은 템플릿 자체는 실체화가 일어나기 전에는 자료형의 속성을 결정하지 못하는 걸 알 수 있다. 정적이 아닌 데이터 멤버는 클래스에 귀속되며 컴파일될 때 자료형이 항상 정해져야 한다.

11. 자가 연역

[include(틀:C++ 요소,
kw1=class,
cls_attribute=특성,
pre1_t=클래스-식별자)]
#!if head_comment!=null
{{{#57A64A,#5EBD46 }}}
#!if head_lnb!=null
##======================================= include and import
[br]
#!if import!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{import }}}}}}'''{{{#C8865E {{{<>}}}}}}{{{;}}}
#!if include!=null
##======================================= the keyword at head (can be `template`)
{{{#include <>}}}
#!if (head_keyword_available = head_keyword != null)
##======================================= template parameters begin
'''{{{#DodgerBlue,#CornFlowerBlue {{{}}}}}}'''
#!if template_available = (template_p0 != null || template_v0 != null || template_p1 != null || template_v1 != null || template_p2 != null || template_v2 != null || template_p3 != null || template_v3 != null)
#!if template_available || template_last_label != null
##======================================= template parameter 0
##======================================= template parameter 0 concept
{{{<}}}{{{#!if template_concept0_available = (template_cpt0 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept0_params_available = (template_concept0_p0 != null || template_concept0_p1 != null || template_concept0_p2 != null || template_concept0_p3 != null)
{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{<}}}}}}{{{#!if template_concept0_params_available
##======================================= template parameter 0 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p0 != null || template_concept0_v0 != null) && (template_concept0_p1 != null || template_concept0_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p1 != null || template_concept0_v1 != null) && (template_concept0_p2 != null || template_concept0_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p2 != null || template_concept0_v2 != null) && (template_concept0_p3 != null || template_concept0_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept0_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept0_available
{{{ }}}}}}{{{#!if template_p0_available = (template_p0 != null)
{{{#!if !template_concept0_available
##======================================= template parameter 0 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v0_available = (template_v0 != null)
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}
##======================================= template parameter 0 finished
}}}{{{#!if (template_p0_available || template_v0_available) && (template_p1 != null || template_v1 != null)
##======================================= template parameter 1
{{{, }}}}}}{{{#!if template_concept1_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept1_params_available = (template_concept1_p0 != null || template_concept1_p1 != null || template_concept1_p2 != null || template_concept1_p3 != null)
{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{<}}}}}}{{{#!if template_concept1_params_available
##======================================= template parameter 1 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p0 != null || template_concept1_v0 != null) && (template_concept1_p1 != null || template_concept1_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p1 != null || template_concept1_v1 != null) && (template_concept1_p2 != null || template_concept1_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p2 != null || template_concept1_v2 != null) && (template_concept1_p3 != null || template_concept1_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept1_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept1_available
{{{ }}}}}}{{{#!if template_p1_available = (template_p1 != null)
{{{#!if !template_concept1_available
##======================================= template parameter 1 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}}}}{{{#!if template_v1_available = (template_v1 != null)
##======================================= template parameter 1 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if (template_p1_available || template_v1_available) && (template_p2 != null || template_v2 != null)
##======================================= template parameter 2
{{{, }}}}}}{{{#!if template_concept2_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept2_params_available = (template_concept2_p0 != null || template_concept2_p1 != null || template_concept2_p2 != null || template_concept2_p3 != null)
{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{<}}}}}}{{{#!if template_concept2_params_available
##======================================= template parameter 2 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p0 != null || template_concept2_v0 != null) && (template_concept2_p1 != null || template_concept2_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p1 != null || template_concept2_v1 != null) && (template_concept2_p2 != null || template_concept2_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p2 != null || template_concept2_v2 != null) && (template_concept2_p3 != null || template_concept2_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept2_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept2_available
{{{ }}}}}}{{{#!if template_p2_available = (template_p2 != null)
{{{#!if !template_concept2_available
##======================================= template parameter 2 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v2_available = (template_v2 != null)
##======================================= template parameter 2 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if template_available && template_last_label != null
##======================================= template finished
{{{, }}}}}}{{{#!if template_last_label != null
{{{...}}}}}}{{{#!if template_available || template_last_label != null
{{{>}}}}}}{{{#!if do_template_linebreak = (template_lnb != null)
##======================================= template linebreak
[br]}}}{{{#!if do_template_linebreak ;nbsp
}}}
#!if head_keyword_available!=null
{{{ }}}
#!if fn_attribute!=null
##======================================= contents
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr1]]}}}}}}]] 
#!if fn_attribute_lnk!=null
[[C++/문법/특성#attr1|{{{#a8a8a8 {{{[[attr1]]}}}}}}]] 
#!if fn_attribute_lnb!=null
[br]
#!if fn_attribute_lnb!=null ;nbsp
#!if kw1!=null
'''{{{#569cd6,#CornFlowerBlue {{{{}}}}}}'''{{{#!if kw1_post!=null
{{{_kwp1}}}}}}{{{#!if kw1_post==null
{{{ }}}}}}
#!if kw2!=null
'''{{{#569cd6,#CornFlowerBlue {{{long long}}}}}}'''{{{#!if kw2post!=null
{{{&&}}}}}}{{{#!if kw2_post==null
{{{ }}}}}}
#!if cls_attribute!=null
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr2]]}}}}}}]]{{{ }}}
#!if cls_attribute_lnk!=null
[[C++/문법/특성#attr2|{{{#a8a8a8 {{{[[attr2]] }}}}}}]]
#!if ns1!=null
##======================================= namespaces
'''{{{#58fafe {{{std}}}}}}'''
#!if ns2!=null
{{{::}}}'''{{{#58fafe {{{chrono}}}}}}'''
#!if ns1!=null
{{{::}}}
#!if pre1_t!=null
##======================================= types at the front
{{{#4ec9b0,#6fdbba {{{system_clock }}}}}}
#!if pre2_t!=null
{{{::}}}{{{#4ec9b0,#6fdbba {{{duration }}}}}}
#!if pre_e!=null
{{{::}}}{{{#f0f068 {{{enum }}}}}}
#!if pre_post!=null
{{{_pre}}}
#!if pre_lnb!=null
[br] 
#!if do_pre_linebreak = (pre_lnb != null) &nbsp;
##======================================= pre-body things finished
#!if (!do_pre_linebreak && !do_template_linebreak && head_keyword_available) && (body_v != null || body_f != null || body_mv != null || body_mf != null || body_post != null)
{{{ }}}
#!if body_v!=null
##======================================= identifiers of variable and function
{{{#a8a8a8,#a1a1a1 {{{val}}}}}}
#!if body_mv!=null
{{{#ffffff {{{mem_val}}}}}}
#!if body_f!=null
{{{#f87a7a {{{fun}}}}}}
#!if body_mf!=null
{{{#f0a962 {{{mem_fn}}}}}}
#!if body_post!=null
##======================================= argument 1
{{{}}}
#!if body_tmpopen!=null
{{{<}}}
#!if body_bopen!=null
{{{(}}}
#!if arg1_concept!=null
'''{{{#4ec9b0,#6fdbba {{{concept1}}}}}}'''{{{#!if arg1_concept_params!=null
{{{<}}}{{{#!if arg1_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg1_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg1_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg1_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg1_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg1_t!=null
{{{#4ec9b0,#6fdbba {{{type1}}}}}}
#!if arg1_t_post!=null
{{{&}}}
#!if arg1_param != null && (arg1_kw != null || arg1_t_kw != null || arg1_t != null)
{{{ }}}
#!if arg1_param!=null
{{{#bcdce6 {{{param1}}}}}}
#!if arg2_concept != null || arg2_kw != null || arg2_t_kw != null || arg2_t != null || arg2_param != null
{{{, }}}
#!if arg2_concept!=null
##======================================= argument 2
'''{{{#4ec9b0,#6fdbba {{{concept2}}}}}}'''{{{#!if arg2_concept_params!=null
{{{<}}}{{{#!if arg2_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg2_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg2_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg2_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg2_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg2_t!=null
{{{#4ec9b0,#6fdbba {{{type2}}}}}}
#!if arg2_t_post!=null
{{{&}}}
#!if arg2_param!=null && (arg2_kw != null || arg2_t_kw != null || arg2_t != null)
{{{ }}}
#!if arg2_param!=null
{{{#bcdce6 {{{param2}}}}}}
#!if arg3_concept != null || arg3_kw != null || arg3_t_kw != null || arg3_t != null || arg3_param != null
{{{, }}}
#!if arg3_concept!=null
##======================================= argument 3
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg3_concept_params!=null
{{{<}}}{{{#!if arg3_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg3_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg3_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg3_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg3_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg3_t!=null
{{{#4ec9b0,#6fdbba {{{type3}}}}}}
#!if arg3_t_post!=null
{{{&}}}
#!if arg3_param!=null && (arg3_kw != null || arg3_t_kw != null || arg3_t != null)
{{{ }}}
#!if arg3_param!=null
{{{#bcdce6 {{{param3}}}}}}
#!if arg4_concept != null || arg4_kw != null || arg4_t_kw != null || arg4_t != null || arg4_param != null
{{{, }}}
#!if arg4_concept!=null
##======================================= argument4
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg4_concept_params!=null
{{{<}}}{{{#!if arg4_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg4_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg4_concept_tparam4!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg4_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg4_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg4_t!=null
{{{#4ec9b0,#6fdbba {{{type4}}}}}}
#!if arg4_t_post!=null
{{{&}}}
#!if arg4_param!=null && (arg4_kw != null || arg4_t_kw != null || arg4_t != null)
{{{ }}}
#!if arg4_param!=null
{{{#bcdce6 {{{param4}}}}}}
#!if arg5_param!=null
##======================================= argument5, argument6
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg6_param != null
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg_last_dots != null
{{{, ...}}}
#!if body_bclose!=null
##=======================================
{{{)}}}
#!if body_lnb!=null
[br] 
#!if body_lnb!=null
{{{ }}}
#!if body_spec1!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{ const}}}}}}'''
#!if body_spec1_paren != null
{{{(}}}
#!if body_spec1_ref!=null
{{{&}}}
#!if body_spec2!=null
{{{#!if body_spec1!=null && body_spec1_paren == null
{{{ }}}}}}'''{{{#DodgerBlue,#CornFlowerBlue {{{noexcept}}}}}}'''
#!if body_spec2_paren != null
{{{(}}}
#!if body_spec2_label != null
{{{...}}}
#!if body_spec2_paren != null
{{{)}}}
#!if body_spec1_paren != null
{{{)}}}
#!if body_tmpclose!=null
##======================================= last labels
{{{>}}}
#!if label_last!=null
{{{}}}
#!if last!=null
{{{;}}}


[include(틀:C++ 요소, head_keyword=template,
template_p0=\[자가-템플릿-매개변수\], template_p1=템플릿-매개변수1,
template_last_label=..., template_lnb=1,
fn_attribute=특성,
pre1_t=반환-자료형, body_mf=멤버-함수-식별자,
arg1_kw=this, arg1_t=\[자가-템플릿-매개변수\], arg1_t_post=[자가-참조자], arg1_param=[자가-매개변수],
arg2_t=매개변수1-자료형, arg2_param=매개변수1,
arg_last_dots=1,
body_bopen=1, body_bclose=1, last=;)]
[include(틀:C++ 요소, head_keyword=template,
template_p0=템플릿-매개변수1,
template_last_label=..., template_lnb=1,
fn_attribute=특성,
pre1_t=반환-자료형, body_mf=멤버-함수-식별자,
arg1_kw=this, arg1_t_kw=auto, arg1_t_post=[자가-참조자], arg1_param=[자가-매개변수],
arg2_t=매개변수1-자료형, arg2_param=매개변수1,
arg_last_dots=1,
body_bopen=1, body_bclose=1, last=;)]
#!if head_comment!=null
{{{#57A64A,#5EBD46 }}}
#!if head_lnb!=null
##======================================= include and import
[br]
#!if import!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{import }}}}}}'''{{{#C8865E {{{<>}}}}}}{{{;}}}
#!if include!=null
##======================================= the keyword at head (can be `template`)
{{{#include <>}}}
#!if (head_keyword_available = head_keyword != null)
##======================================= template parameters begin
'''{{{#DodgerBlue,#CornFlowerBlue {{{}}}}}}'''
#!if template_available = (template_p0 != null || template_v0 != null || template_p1 != null || template_v1 != null || template_p2 != null || template_v2 != null || template_p3 != null || template_v3 != null)
#!if template_available || template_last_label != null
##======================================= template parameter 0
##======================================= template parameter 0 concept
{{{<}}}{{{#!if template_concept0_available = (template_cpt0 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept0_params_available = (template_concept0_p0 != null || template_concept0_p1 != null || template_concept0_p2 != null || template_concept0_p3 != null)
{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{<}}}}}}{{{#!if template_concept0_params_available
##======================================= template parameter 0 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p0 != null || template_concept0_v0 != null) && (template_concept0_p1 != null || template_concept0_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p1 != null || template_concept0_v1 != null) && (template_concept0_p2 != null || template_concept0_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p2 != null || template_concept0_v2 != null) && (template_concept0_p3 != null || template_concept0_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept0_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept0_available
{{{ }}}}}}{{{#!if template_p0_available = (template_p0 != null)
{{{#!if !template_concept0_available
##======================================= template parameter 0 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v0_available = (template_v0 != null)
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}
##======================================= template parameter 0 finished
}}}{{{#!if (template_p0_available || template_v0_available) && (template_p1 != null || template_v1 != null)
##======================================= template parameter 1
{{{, }}}}}}{{{#!if template_concept1_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept1_params_available = (template_concept1_p0 != null || template_concept1_p1 != null || template_concept1_p2 != null || template_concept1_p3 != null)
{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{<}}}}}}{{{#!if template_concept1_params_available
##======================================= template parameter 1 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p0 != null || template_concept1_v0 != null) && (template_concept1_p1 != null || template_concept1_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p1 != null || template_concept1_v1 != null) && (template_concept1_p2 != null || template_concept1_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p2 != null || template_concept1_v2 != null) && (template_concept1_p3 != null || template_concept1_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept1_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept1_available
{{{ }}}}}}{{{#!if template_p1_available = (template_p1 != null)
{{{#!if !template_concept1_available
##======================================= template parameter 1 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}}}}{{{#!if template_v1_available = (template_v1 != null)
##======================================= template parameter 1 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if (template_p1_available || template_v1_available) && (template_p2 != null || template_v2 != null)
##======================================= template parameter 2
{{{, }}}}}}{{{#!if template_concept2_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept2_params_available = (template_concept2_p0 != null || template_concept2_p1 != null || template_concept2_p2 != null || template_concept2_p3 != null)
{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{<}}}}}}{{{#!if template_concept2_params_available
##======================================= template parameter 2 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p0 != null || template_concept2_v0 != null) && (template_concept2_p1 != null || template_concept2_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p1 != null || template_concept2_v1 != null) && (template_concept2_p2 != null || template_concept2_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p2 != null || template_concept2_v2 != null) && (template_concept2_p3 != null || template_concept2_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept2_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept2_available
{{{ }}}}}}{{{#!if template_p2_available = (template_p2 != null)
{{{#!if !template_concept2_available
##======================================= template parameter 2 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v2_available = (template_v2 != null)
##======================================= template parameter 2 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if template_available && template_last_label != null
##======================================= template finished
{{{, }}}}}}{{{#!if template_last_label != null
{{{...}}}}}}{{{#!if template_available || template_last_label != null
{{{>}}}}}}{{{#!if do_template_linebreak = (template_lnb != null)
##======================================= template linebreak
[br]}}}{{{#!if do_template_linebreak ;nbsp
}}}
#!if head_keyword_available!=null
{{{ }}}
#!if fn_attribute!=null
##======================================= contents
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr1]]}}}}}}]] 
#!if fn_attribute_lnk!=null
[[C++/문법/특성#attr1|{{{#a8a8a8 {{{[[attr1]]}}}}}}]] 
#!if fn_attribute_lnb!=null
[br]
#!if fn_attribute_lnb!=null ;nbsp
#!if kw1!=null
'''{{{#569cd6,#CornFlowerBlue {{{}}}}}}}'''{{{#!if kw1_post!=null
{{{_kwp1}}}}}}{{{#!if kw1_post==null
{{{ }}}}}}
#!if kw2!=null
'''{{{#569cd6,#CornFlowerBlue {{{long long}}}}}}'''{{{#!if kw2post!=null
{{{&&}}}}}}{{{#!if kw2_post==null
{{{ }}}}}}
#!if cls_attribute!=null
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr2]]}}}}}}]]{{{ }}}
#!if cls_attribute_lnk!=null
[[C++/문법/특성#attr2|{{{#a8a8a8 {{{[[attr2]] }}}}}}]]
#!if ns1!=null
##======================================= namespaces
'''{{{#58fafe {{{std}}}}}}'''
#!if ns2!=null
{{{::}}}'''{{{#58fafe {{{chrono}}}}}}'''
#!if ns1!=null
{{{::}}}
#!if pre1_t!=null
##======================================= types at the front
{{{#4ec9b0,#6fdbba {{{system_clock }}}}}}
#!if pre2_t!=null
{{{::}}}{{{#4ec9b0,#6fdbba {{{duration }}}}}}
#!if pre_e!=null
{{{::}}}{{{#f0f068 {{{enum }}}}}}
#!if pre_post!=null
{{{_pre}}}
#!if pre_lnb!=null
[br] 
#!if do_pre_linebreak = (pre_lnb != null) &nbsp;
##======================================= pre-body things finished
#!if (!do_pre_linebreak && !do_template_linebreak && head_keyword_available) && (body_v != null || body_f != null || body_mv != null || body_mf != null || body_post != null)
{{{ }}}
#!if body_v!=null
##======================================= identifiers of variable and function
{{{#a8a8a8,#a1a1a1 {{{val}}}}}}
#!if body_mv!=null
{{{#ffffff {{{mem_val}}}}}}
#!if body_f!=null
{{{#f87a7a {{{fun}}}}}}
#!if body_mf!=null
{{{#f0a962 {{{mem_fn}}}}}}
#!if body_post!=null
##======================================= argument 1
{{{}}}
#!if body_tmpopen!=null
{{{<}}}
#!if body_bopen!=null
{{{(}}}
#!if arg1_concept!=null
'''{{{#4ec9b0,#6fdbba {{{concept1}}}}}}'''{{{#!if arg1_concept_params!=null
{{{<}}}{{{#!if arg1_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg1_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg1_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg1_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg1_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg1_t!=null
{{{#4ec9b0,#6fdbba {{{type1}}}}}}
#!if arg1_t_post!=null
{{{&}}}
#!if arg1_param != null && (arg1_kw != null || arg1_t_kw != null || arg1_t != null)
{{{ }}}
#!if arg1_param!=null
{{{#bcdce6 {{{param1}}}}}}
#!if arg2_concept != null || arg2_kw != null || arg2_t_kw != null || arg2_t != null || arg2_param != null
{{{, }}}
#!if arg2_concept!=null
##======================================= argument 2
'''{{{#4ec9b0,#6fdbba {{{concept2}}}}}}'''{{{#!if arg2_concept_params!=null
{{{<}}}{{{#!if arg2_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg2_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg2_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg2_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg2_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg2_t!=null
{{{#4ec9b0,#6fdbba {{{type2}}}}}}
#!if arg2_t_post!=null
{{{&}}}
#!if arg2_param!=null && (arg2_kw != null || arg2_t_kw != null || arg2_t != null)
{{{ }}}
#!if arg2_param!=null
{{{#bcdce6 {{{param2}}}}}}
#!if arg3_concept != null || arg3_kw != null || arg3_t_kw != null || arg3_t != null || arg3_param != null
{{{, }}}
#!if arg3_concept!=null
##======================================= argument 3
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg3_concept_params!=null
{{{<}}}{{{#!if arg3_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg3_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg3_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg3_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg3_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg3_t!=null
{{{#4ec9b0,#6fdbba {{{type3}}}}}}
#!if arg3_t_post!=null
{{{&}}}
#!if arg3_param!=null && (arg3_kw != null || arg3_t_kw != null || arg3_t != null)
{{{ }}}
#!if arg3_param!=null
{{{#bcdce6 {{{param3}}}}}}
#!if arg4_concept != null || arg4_kw != null || arg4_t_kw != null || arg4_t != null || arg4_param != null
{{{, }}}
#!if arg4_concept!=null
##======================================= argument4
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg4_concept_params!=null
{{{<}}}{{{#!if arg4_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg4_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg4_concept_tparam4!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg4_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg4_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg4_t!=null
{{{#4ec9b0,#6fdbba {{{type4}}}}}}
#!if arg4_t_post!=null
{{{&}}}
#!if arg4_param!=null && (arg4_kw != null || arg4_t_kw != null || arg4_t != null)
{{{ }}}
#!if arg4_param!=null
{{{#bcdce6 {{{param4}}}}}}
#!if arg5_param!=null
##======================================= argument5, argument6
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg6_param != null
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg_last_dots != null
{{{, ...}}}
#!if body_bclose!=null
##=======================================
{{{)}}}
#!if body_lnb!=null
[br] 
#!if body_lnb!=null
{{{ }}}
#!if body_spec1!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{ const}}}}}}'''
#!if body_spec1_paren != null
{{{(}}}
#!if body_spec1_ref!=null
{{{&}}}
#!if body_spec2!=null
{{{#!if body_spec1!=null && body_spec1_paren == null
{{{ }}}}}}'''{{{#DodgerBlue,#CornFlowerBlue {{{noexcept}}}}}}'''
#!if body_spec2_paren != null
{{{(}}}
#!if body_spec2_label != null
{{{...}}}
#!if body_spec2_paren != null
{{{)}}}
#!if body_spec1_paren != null
{{{)}}}
#!if body_tmpclose!=null
##======================================= last labels
{{{>}}}
#!if label_last!=null
{{{}}}
#!if last!=null
{{{;}}}


객체 명시 매개변수
혹시 클래스의 멤버 함수에 한정자와 참조자를 적용할 수 있던 사실을 기억하는가? const, volatile, mutable에다가 &, &&까지 상상할 수 있는 모든 경우에 대해 코드를 쓸 수 있었다. 하지만 이걸 실제로 코드에 쓰는 건 말도 안되는 환장할 노가다나 다를 게 없다. 클래스 문서에도 나와있는 표준에 쓰인 사례는 한 손에 꼽는다. 하지만 없으면 그 특별한 사례에서 큰 문제를 일으키기에 필요한 기능이기도 하다.
#!syntax cpp
class Squirrel
{
public:
    // (1) 완벽한 전달로 모든 경우의 수를 포괄하는 원본 멤버 함수 템플릿
    template<typename Self>
    constexpr bool IsMoving(this Self&& self) noexcept
    {
        return true;
    }

    // (2) lvalue에 대하여 오버로딩
    template<typename Self>
    constexpr bool IsMoving(this Self& self) noexcept
    {
        return false;
    }
};

int main()
{
    Squirrel squirrel{};
    const Squirrel csquirrel{};

    // (3) `mov0`의 값은 false
    bool mov0 = squirrel.IsMoving();

    // (4) `mov1`의 값은 true
    bool mov1 = std::move(squirrel).IsMoving();

    // (5) `mov2`의 값은 false
    bool mov2 = csquirrel.IsMoving();

    // (6) `mov3`의 값은 true
    bool mov3 = std::move(csquirrel).IsMoving();
}
일명 자가 연역(Deducing This)이라고 불리는 객체 명시 기능은 멤버 함수의 첫번째 템플릿 매개변수 & 첫번째 함수 매개변수에 클래스 인스턴스를 완벽하게 전달할 수 있는 기능이다. 파이썬, Rust 등에서 영감을 얻어 추가된 기능으로써 멤버 함수의 한정자를 노가다 없이 작성할 수 있으며 최적화 문제도 깔끔하게 해결한다. 멤버 함수의 명세는 객체 명시 멤버 함수를 참고하자.

12. 클래스 템플릿

12.1. 멤버 함수

12.2. 생성자

클래스 템플릿 인자 연역 (Class Template Argument Deduction)

12.3. 연산자 오버로딩

13. 템플릿 연역 유도문

템플릿 연역 유도문 (Template Deducing Guide)
C++17부터 클래스의 생성자에 대하여 사용자가 직접 컴파일러한테 추론 방식을 지시할 수 있다.

14. 템플릿 특수화

템플릿 특수화 (Template Specialization)
템플릿의 매개변수는 사용자가 전부 혹은 일부분을 명시할 수 있다. 이때 모든 템플릿 매개변수를 대체하면 전체 템플릿 특수화, 일부만 대체하면 부분 템플릿 특수화라고 한다. 각 사례에 대한 설명은 이후 문단에서 따로 한다.

14.1. 전체 특수화

#!if head_comment!=null
{{{#57A64A,#5EBD46 }}}
#!if head_lnb!=null
##======================================= include and import
[br]
#!if import!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{import }}}}}}'''{{{#C8865E {{{<>}}}}}}{{{;}}}
#!if include!=null
##======================================= the keyword at head (can be `template`)
{{{#include <>}}}
#!if (head_keyword_available = head_keyword != null)
##======================================= template parameters begin
'''{{{#DodgerBlue,#CornFlowerBlue {{{template}}}}}}'''
#!if template_available = (template_p0 != null || template_v0 != null || template_p1 != null || template_v1 != null || template_p2 != null || template_v2 != null || template_p3 != null || template_v3 != null)
#!if template_available || template_last_label != null
##======================================= template parameter 0
##======================================= template parameter 0 concept
{{{<}}}{{{#!if template_concept0_available = (template_cpt0 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept0_params_available = (template_concept0_p0 != null || template_concept0_p1 != null || template_concept0_p2 != null || template_concept0_p3 != null)
{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{<}}}}}}{{{#!if template_concept0_params_available
##======================================= template parameter 0 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p0 != null || template_concept0_v0 != null) && (template_concept0_p1 != null || template_concept0_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p1 != null || template_concept0_v1 != null) && (template_concept0_p2 != null || template_concept0_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p2 != null || template_concept0_v2 != null) && (template_concept0_p3 != null || template_concept0_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept0_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept0_available
{{{ }}}}}}{{{#!if template_p0_available = (template_p0 != null)
{{{#!if !template_concept0_available
##======================================= template parameter 0 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v0_available = (template_v0 != null)
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}
##======================================= template parameter 0 finished
}}}{{{#!if (template_p0_available || template_v0_available) && (template_p1 != null || template_v1 != null)
##======================================= template parameter 1
{{{, }}}}}}{{{#!if template_concept1_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept1_params_available = (template_concept1_p0 != null || template_concept1_p1 != null || template_concept1_p2 != null || template_concept1_p3 != null)
{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{<}}}}}}{{{#!if template_concept1_params_available
##======================================= template parameter 1 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p0 != null || template_concept1_v0 != null) && (template_concept1_p1 != null || template_concept1_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p1 != null || template_concept1_v1 != null) && (template_concept1_p2 != null || template_concept1_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p2 != null || template_concept1_v2 != null) && (template_concept1_p3 != null || template_concept1_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept1_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept1_available
{{{ }}}}}}{{{#!if template_p1_available = (template_p1 != null)
{{{#!if !template_concept1_available
##======================================= template parameter 1 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}}}}{{{#!if template_v1_available = (template_v1 != null)
##======================================= template parameter 1 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if (template_p1_available || template_v1_available) && (template_p2 != null || template_v2 != null)
##======================================= template parameter 2
{{{, }}}}}}{{{#!if template_concept2_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept2_params_available = (template_concept2_p0 != null || template_concept2_p1 != null || template_concept2_p2 != null || template_concept2_p3 != null)
{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{<}}}}}}{{{#!if template_concept2_params_available
##======================================= template parameter 2 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p0 != null || template_concept2_v0 != null) && (template_concept2_p1 != null || template_concept2_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p1 != null || template_concept2_v1 != null) && (template_concept2_p2 != null || template_concept2_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p2 != null || template_concept2_v2 != null) && (template_concept2_p3 != null || template_concept2_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept2_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept2_available
{{{ }}}}}}{{{#!if template_p2_available = (template_p2 != null)
{{{#!if !template_concept2_available
##======================================= template parameter 2 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v2_available = (template_v2 != null)
##======================================= template parameter 2 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if template_available && template_last_label != null
##======================================= template finished
{{{, }}}}}}{{{#!if template_last_label != null
{{{...}}}}}}{{{#!if template_available || template_last_label != null
{{{>}}}}}}{{{#!if do_template_linebreak = (template_lnb != null)
##======================================= template linebreak
[br]}}}{{{#!if do_template_linebreak ;nbsp
}}}
#!if head_keyword_available!=null
{{{ }}}
#!if fn_attribute!=null
##======================================= contents
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr1]]}}}}}}]] 
#!if fn_attribute_lnk!=null
[[C++/문법/특성#attr1|{{{#a8a8a8 {{{[[attr1]]}}}}}}]] 
#!if fn_attribute_lnb!=null
[br]
#!if fn_attribute_lnb!=null ;nbsp
#!if kw1!=null
'''{{{#569cd6,#CornFlowerBlue {{{contexpr}}}}}}'''{{{#!if kw1_post!=null
{{{_kwp1}}}}}}{{{#!if kw1_post==null
{{{ }}}}}}
#!if kw2!=null
'''{{{#569cd6,#CornFlowerBlue {{{long long}}}}}}'''{{{#!if kw2post!=null
{{{&&}}}}}}{{{#!if kw2_post==null
{{{ }}}}}}
#!if cls_attribute!=null
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr2]]}}}}}}]]{{{ }}}
#!if cls_attribute_lnk!=null
[[C++/문법/특성#attr2|{{{#a8a8a8 {{{[[attr2]] }}}}}}]]
#!if ns1!=null
##======================================= namespaces
'''{{{#58fafe {{{std}}}}}}'''
#!if ns2!=null
{{{::}}}'''{{{#58fafe {{{chrono}}}}}}'''
#!if ns1!=null
{{{::}}}
#!if pre1_t!=null
##======================================= types at the front
{{{#4ec9b0,#6fdbba {{{system_clock }}}}}}
#!if pre2_t!=null
{{{::}}}{{{#4ec9b0,#6fdbba {{{duration }}}}}}
#!if pre_e!=null
{{{::}}}{{{#f0f068 {{{enum }}}}}}
#!if pre_post!=null
{{{_pre}}}
#!if pre_lnb!=null
[br] 
#!if do_pre_linebreak = (pre_lnb != null) &nbsp;
##======================================= pre-body things finished
#!if (!do_pre_linebreak && !do_template_linebreak && head_keyword_available) && (body_v != null || body_f != null || body_mv != null || body_mf != null || body_post != null)
{{{ }}}
#!if body_v!=null
##======================================= identifiers of variable and function
{{{#a8a8a8,#a1a1a1 {{{val}}}}}}
#!if body_mv!=null
{{{#ffffff {{{mem_val}}}}}}
#!if body_f!=null
{{{#f87a7a {{{fun}}}}}}
#!if body_mf!=null
{{{#f0a962 {{{mem_fn}}}}}}
#!if body_post!=null
##======================================= argument 1
{{{}}}
#!if body_tmpopen!=null
{{{<}}}
#!if body_bopen!=null
{{{(}}}
#!if arg1_concept!=null
'''{{{#4ec9b0,#6fdbba {{{concept1}}}}}}'''{{{#!if arg1_concept_params!=null
{{{<}}}{{{#!if arg1_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg1_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg1_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg1_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg1_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg1_t!=null
{{{#4ec9b0,#6fdbba {{{type1}}}}}}
#!if arg1_t_post!=null
{{{&}}}
#!if arg1_param != null && (arg1_kw != null || arg1_t_kw != null || arg1_t != null)
{{{ }}}
#!if arg1_param!=null
{{{#bcdce6 {{{param1}}}}}}
#!if arg2_concept != null || arg2_kw != null || arg2_t_kw != null || arg2_t != null || arg2_param != null
{{{, }}}
#!if arg2_concept!=null
##======================================= argument 2
'''{{{#4ec9b0,#6fdbba {{{concept2}}}}}}'''{{{#!if arg2_concept_params!=null
{{{<}}}{{{#!if arg2_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg2_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg2_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg2_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg2_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg2_t!=null
{{{#4ec9b0,#6fdbba {{{type2}}}}}}
#!if arg2_t_post!=null
{{{&}}}
#!if arg2_param!=null && (arg2_kw != null || arg2_t_kw != null || arg2_t != null)
{{{ }}}
#!if arg2_param!=null
{{{#bcdce6 {{{param2}}}}}}
#!if arg3_concept != null || arg3_kw != null || arg3_t_kw != null || arg3_t != null || arg3_param != null
{{{, }}}
#!if arg3_concept!=null
##======================================= argument 3
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg3_concept_params!=null
{{{<}}}{{{#!if arg3_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg3_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg3_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg3_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg3_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg3_t!=null
{{{#4ec9b0,#6fdbba {{{type3}}}}}}
#!if arg3_t_post!=null
{{{&}}}
#!if arg3_param!=null && (arg3_kw != null || arg3_t_kw != null || arg3_t != null)
{{{ }}}
#!if arg3_param!=null
{{{#bcdce6 {{{param3}}}}}}
#!if arg4_concept != null || arg4_kw != null || arg4_t_kw != null || arg4_t != null || arg4_param != null
{{{, }}}
#!if arg4_concept!=null
##======================================= argument4
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg4_concept_params!=null
{{{<}}}{{{#!if arg4_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg4_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg4_concept_tparam4!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg4_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg4_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg4_t!=null
{{{#4ec9b0,#6fdbba {{{type4}}}}}}
#!if arg4_t_post!=null
{{{&}}}
#!if arg4_param!=null && (arg4_kw != null || arg4_t_kw != null || arg4_t != null)
{{{ }}}
#!if arg4_param!=null
{{{#bcdce6 {{{param4}}}}}}
#!if arg5_param!=null
##======================================= argument5, argument6
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg6_param != null
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg_last_dots != null
{{{, ...}}}
#!if body_bclose!=null
##=======================================
{{{)}}}
#!if body_lnb!=null
[br] 
#!if body_lnb!=null
{{{ }}}
#!if body_spec1!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{ const}}}}}}'''
#!if body_spec1_paren != null
{{{(}}}
#!if body_spec1_ref!=null
{{{&}}}
#!if body_spec2!=null
{{{#!if body_spec1!=null && body_spec1_paren == null
{{{ }}}}}}'''{{{#DodgerBlue,#CornFlowerBlue {{{noexcept}}}}}}'''
#!if body_spec2_paren != null
{{{(}}}
#!if body_spec2_label != null
{{{...}}}
#!if body_spec2_paren != null
{{{)}}}
#!if body_spec1_paren != null
{{{)}}}
#!if body_tmpclose!=null
##======================================= last labels
{{{>}}}
#!if label_last!=null
{{{}}}
#!if last!=null
{{{;}}}


전체 템플릿 특수화 (Full Template Specialization)
템플릿의 모든 템플릿 매개변수를 대신하여 사용자가 직접 명시하면 전체 템플릿 특수화라고 칭한다. C++ 코드에서는 매개변수를 빼고 template<>만 적은 다음, 템플릿 매개변수가 사용된 자리를 대체하면 된다.

14.1.1. 함수 템플릿 특수화

[include(틀:C++ 요소, head_keyword=template,
template_last_label=, template_lnb=1,
fn_attribute=특성, fn_attribute_lnb=1,
pre1_t=반환-자료형, body_f=함수-식별자,
arg1_t=매개변수1-자료형, arg1_param=매개변수1, arg2_t=매개변수2-자료형, arg2_param=매개변수2, arg_last_dots=1,
body_bopen=1, body_bclose=1, last=;)]
전체 함수 템플릿 특수화 (Full Specializations of Function Templates)
함수의 모든 템플릿 매개변수를 대체하면 전체 함수 템플릿 특수화다.

#!syntax cpp
// (1) 모든 함수 사양을 포괄하는 함수 템플릿
template<typename T>
void Function(T value);

// (2) (1)의 특수화
template<>
void Function(int value);
함수 오버로딩과는 조금 다른 기능이므로 주의하자. 오버로딩과의 차이는 이후 문단에서 설명한다.

14.2. 부분 특수화

부분 템플릿 특수화 (Partial Template Specialization)
클래스 템플릿과 변수 템플릿에 적용할 수 있다.

15. 함수 템플릿 오버로딩

[include(틀:C++ 요소, head_keyword=template,
template_p0=템플릿-매개변수1, template_p1=템플릿-매개변수2,
template_last_label=..., template_lnb=1,
fn_attribute=특성, fn_attribute_lnb=1,
pre1_t=반환-자료형, body_f=함수-식별자,
arg1_t=매개변수1-자료형, arg1_param=매개변수1, arg2_t=매개변수2-자료형, arg2_param=매개변수2, arg_last_dots=1,
body_bopen=1, body_bclose=1 , last=;)]

[include(틀:C++ 요소, head_keyword=template,
template_p0=템플릿-매개변수3, template_p1=템플릿-매개변수4,
template_last_label=..., template_lnb=1,
fn_attribute=특성, fn_attribute_lnb=1,
pre1_t=반환-자료형, body_f=함수-식별자,
arg1_t=매개변수3-자료형, arg1_param=매개변수3, arg2_t=매개변수4-자료형, arg2_param=매개변수4, arg_last_dots=1,
body_bopen=1, body_bclose=1 , last=;)]
함수 템플릿도 보통 함수와 마찬가지로 오버로딩이 가능하다. 오버로딩된 함수 템플릿은 실행 순서에 차이가 있다. 오버로딩된 함수가 먼저 실행 후보로 고려되고, 원본 템플릿은 우선순위가 뒤쪽이다. 원본 함수 템플릿은 모든 템플릿 사례를 포괄하는 비상창구(Fallback)로서 기능하며 오버로딩한 사례는 사용자가 특별히 원해서 구현한 사례이기 때문이다.

헌데 함수 템플릿의 오버로딩 규칙은 까다롭다. 첫번째 사례는 함수 서명의 매개변수가 바뀐 경우만 오버로딩으로 인정된다.
#!syntax cpp
// (1)
template<typename T>
void Function(T val);

// (2) (1)의 오버로딩이 아니라 별개의 함수 템플릿이다.
// 비슷한 템플릿의 정의가 있는 것만으로는 컴파일 오류는 발생하지 않는다.
// 그러나 경고가 발생할 수는 있다.
template<typename T>
int Function(T val);

// (3) (1)의 오버로딩. 포인터를 받는다.
template<typename T>
void Function(T* val);

int main()
{
    // (4) 모호성이 있다. (1)과 (2) 중 하나를 구분할 수가 없다.
    // 컴파일 오류! 템플릿 인스턴스 중 두 개 이상이 일치합니다.
    Function(1);

    // (5) 반환 값을 쓴다고 명시해도 여전히 모호성이 있다. (1), (2)와 (3) 중 하나를 구분할 수가 없다.
    // 컴파일 오류! 템플릿 인스턴스 중 두 개 이상이 일치합니다.
    auto val = Function(2);
    
    int v = 0;
    // (6) 모호성이 있다. (1), (2)와 (3) 중 하나를 구분할 수가 없다.
    // 컴파일 오류! 템플릿 인스턴스 중 두 개 이상이 일치합니다.
    Function(v);
}
상기 코드는 오버로딩이 인정 안되는 사례를 보여주고 있다. (2)는 (1), (3)과 같은 템플릿 명세를 가지고 있지만, 함수 서명의 반환 자료형이 다르기 때문에 오버로딩이 아니다. 그리고 말썽도 일으킨다. 사실 이 예제는 중간에 오버로딩이 안되는 사례 (2)만 지우면 문제가 없다.
#!syntax cpp
// (1)
template<typename T>
void Function(T val);

// (2) (1)의 오버로딩.
template<typename T>
void Function(T* val);

int main()
{
    // (3) 모호성이 없다. (1)이 선택된다.
    // `T`는 int
    // 템플릿 명세는 `Function<int>`
    // 함수 서명은 `void (int)`
    // 함수 템플릿 인스턴스는 `Function<T>(T)`
    Function(1);

    int v1 = 0;
    // (4) 모호성이 없다. (3)이 선택된다.
    // `T`는 int
    // 템플릿 명세는 `Function<int>`
    // 함수 서명은 `void (int*)`
    // 함수 템플릿 인스턴스는 `Function<T>(T*)`
    Function(&v1);

    const int v2 = 0;
    // (5) 모호성이 없다. (3)이 선택된다.
    // `T`는 const int
    // 템플릿 명세는 `Function<const int>`
    // 함수 서명은 `void (const int*)`
    // 함수 템플릿 인스턴스는 `Function<const int>(const int*)`
    Function(&v2);
}
성공 사례에서 확인할 수 있듯이 원본 (1)과 오버로딩 (2)에서 템플릿 명세Function<T>로 (매개변수 부분이) 동일하다. 함수 서명Function(T)Function(T*)다르다. 오버로딩의 조건을 충족한다. 그러나 짚고 넣어가야 할 점이 있다. 상기 코드의 (3)번과 (4)번은 이해가 될 것이다. 그런데 (5)번은 왜 다른 함수 템플릿을 실행할까? 이유는 상수 참조자를 가리키는 포인터이므로 (5)번에서 전달된 인자는 const int*다. 함수 템플릿의 오버로딩 후보에는 상수를 가리키는 포인터가 없다. 그래서 최대한 찾은 후보는 `T``const int`인 경우로 이 후보가 선택된다.
#!syntax cpp
// (1) 원본 함수 템플릿
template<typename T> void Function(T val);

// (2) (1)의 오버로딩
template<typename T> void Function(T* ptr);

// (3) (1)의 오버로딩
template<typename T> void Function(T& ref);

// (4) (1)의 오버로딩
template<typename T> void Function(const T& ref);

// (5) (1)의 오버로딩
template<typename T> void Function(T&& ref);

int main()
{
    int value = 30;
    const int const_value = 20;

    // (6) 컴파일 오류! 템플릿 인스턴스 중 두 개 이상이 일치합니다.
    Function(value);

    // (7) 컴파일 오류! 템플릿 인스턴스 중 두 개 이상이 일치합니다.
    Function(const_value);

    // (8) 컴파일 오류! 템플릿 인스턴스 중 두 개 이상이 일치합니다.
    Function(10);

    // (9) 컴파일 오류! 템플릿 인스턴스 중 두 개 이상이 일치합니다.
    Function(std::move(value));

    // (10) Function<T>(T*) 선택
    // `T`는 int
    // 템플릿 명세는 `Function<int>`
    // 함수 서명은 `void (int*)`
    // 함수 템플릿 인스턴스는 `Function<int>(int*)`
    Function(&value);

    // (11) Function<T>(T*) 선택
    // `T`는 const int
    // 템플릿 명세는 `Function<const int>`
    // 함수 서명은 `void (const int*)`
    // 함수 템플릿 인스턴스는 `Function<const int>(const int*)`
    Function(&const_value);
}
두번째 조건은 오버로딩한 함수 템플릿은 원본 함수 템플릿의 범위를 넘을 수 없다는 것이다. 상기 코드에서 포인터를 받는 경우를 빼면 전부 모호성이 있어서 컴파일이 실패한다. 그 이유는 (5)번의 함수 템플릿이 완벽한 전달을 수행하기 때문에 다른 템플릿 인스턴스를 다 잡아먹고 (1)번과 충돌하기 때문이다. 때문에 원본 템플릿을 T&&로 만들고, 좌측값 참조자는 std::is_const_v<T>로 검사해서 비상수 좌측값과 상수 좌측값을 함께 처리해야 한다. 말로 하면 어려운데 예제를 직접 보자.
#!syntax cpp
// (1) 모든 사례를 포괄하는 원본 함수 템플릿
template<typename T>
void Function3(T&& val);

// (2)
template<typename T>
void Function3(T& val)
{
    if constexpr (std::is_const_v<T>)
    {
        // 상수 좌측값 참조자
        // `T`는 `const U`다. (`U`는 const로 감싸진 원래 자료형)
        // 템플릿 명세는 `Function<const U>`
        // 함수 서명은 `void(const U&)`
        // 함수 템플릿 인스턴스는 `Function<const U>(const U&)`
    }
    else
    {
        // 비상수 좌측값 참조자
        // 템플릿 명세는 `Function<T>`
        // 함수 서명은 `void(T&)`
        // 함수 템플릿 인스턴스는 `Function<U>(T&)`
    }
}

// (3) (1)의 오버로딩
template<typename T>
void Function3(T* val);
상기 코드는 원본 함수 템플릿을 가장 큰 범위의 매개변수를 받게 고친 예제다. T&&가 좌측값, 우측값, 포인터, 배열 등등을 전부 받는다. 그리고 오버로딩을 통해 좌측값, 포인터의 경우를 특수화했다. 좌측값을 받는 오버로딩의 경우 표준 라이브러리의 상수 템플릿 std::is_const_v<T>를 사용했다. 이때 if constexpr을 사용해서 컴파일 시간에 if문이 실행되도록 최적화했다.

함수 템플릿의 오버로딩과 특수화에 관한 비교
<C++ 예제 보기>
#!syntax cpp
// (1) 모든 함수 사양을 포괄하는 함수 템플릿
template<typename T, typename U>
void Function(T lhs, U rhs);

// (2) 전체 특수화
template<>
void Function(int lhs, int rhs);

// (3) (1)번의 오버로딩
template<typename T>
void Function(T lhs, int rhs);
한편 함수 템플릿에는 부분 특수화의 명세가 없다. 왜냐하면 부분 특수화는 함수 오버로딩과의 모호함 때문이다. 함수 템플릿 오버로딩은 원본 함수 템플릿의 일부 사용례를 빌려다 쓰는 것이다. 만약 부분 특수화의 명세가 있다고 생각해보면, 함수 오버로딩과 구분할 수 없는 사례가 있다. 예를 들어서 템플릿 매개변수가 함수의 매개변수에 쓰였을 때 부분 특수화를 하면, 템플릿 자리가 다른 자료형으로 바뀔 것이다. 그런데 이건 오버로딩의 규칙과 똑같은 상황이므로 구분할 필요가 없다.

16. SFINAE

추론 실패는 오류가 아니다 (Substitution Failure Is Not An Error)
드디어 악명높은 그 개념이 등장했다. SFINAE는 템플릿 인스턴스화 과정에서 사소한 찐빠는 넘어간다는 뜻이다. 정확하게 얘기보자면, (1) 컴파일 과정에서 모든 자료형, 혹은 코드 상에서 템플릿이 쓰인 사례에서 템플릿 인스턴스화를 한다. 템플릿 인스턴스화는 템플릿 매개변수를 실제 자료형으로 치환해서 실제 코드를 생성하는 과정이다. (2) 템플릿 인스턴스화 과정에서 실행되지 않는 코드가 생긴 경우, 즉 오류가 있는 코드가 생긴 경우에 컴파일 오류로 취급하지 않는다.

문법을 알아보기 전에 이런 규칙이 왜 있는지 알아보자.
#!syntax cpp
std::vector<void> list{};
이 규칙이 있는 이유는 템플릿 코드가 항상 모든 자료형에 대하여 구현될 필요가 없기 때문이다. 당장 컨테이너 중에 <vector>만 해도 void는 안 받지 않던가? SFINAE 덕분에 정말로 모든 자료형에 대하여 템플릿 코드를 고려할 필요가 없다. SFINAE가 없으면 프로그램 코드가 무한대로 커져버릴 것이다. 결론적으로 SFINAE가 있어야 템플릿의 구현이 성립되는 것이다.

그러나 단순히 템플릿 최적화 역할이기만 하면 문제가 없을 것이다. 자료형에 대한 연산을 하기 위해 지난 세월 별에 별짓을 했는데, 쓸 수 있는게 SFINAE 밖에 없었다. 템플릿에 자료형을 대입해서 구현할 수 없으면 코드를 생성하지 않는다. 라는 규칙을 어쩔 수 없이 쓰면서 이 과정에서 더러운 실사례가 너무 많이 쌓여버렸다. C++11까지 프로그래머가 자료형에 대하여 직접 무언가를 하거나 제어할 방법이 별로 없어서 울며 겨자먹기로 SFINAE를 가져다 쓴 것이기 때문이다.

한편 std::vector<void>의 사례는 자료형을 명시했음에도 클래스의 구현에 따라 암시적으로 일어난 경우다. 다음 문단에서는 다양한 예시를 들어보자.

16.1. 예제1: 멤버 구현 여부

#!syntax cpp
class Squirrel
{
public:
    void Eat() noexcept {};
};

class Moai
{
public:
    void Idle() noexcept {};
};
상기한 코드에선 먹을 줄 아는 다람쥐, 가만히 있는 모아이를 클래스로 만들었다.
#!syntax cpp
template<typename T>
void MakeEat(T& animal)
{
    animal.Eat();
}
특정 개체에 대하여 `Eat` 멤버 함수를 실행시키는 함수 템플릿이다. 이 함수 템플릿이 실체화될 때, 개체 `animal`이 클래스도 아니고, 멤버 함수 `Eat`을 갖고 있지 않으면 컴파일 오류가 발생한다. 런타임 오류가 아니다. 분명 C++ 컴파일러는 원시 자료형을 포함한 모든 자료형을 대입하고 실행할 수 있는지 판별할 수 있을 것이다. 그러나, SFINAE에 의하여 실제로 실체화되기 전까지는 오류를 발생시키지 않는다. 굳이 오류라고 알려주진 않는다. 실제로 함수 템플릿이 쓰일 때만, 구현 여부를 따지는 것이다.
#!syntax cpp
int main()
{
    Squirrel squirrel{};
    Moai moai{};

    // (1) 클래스 `Squirrel`는 멤버 함수 `Eat`을 가지고 있으므로 문제없이 실행됨.
    // `T`는 Squirrel
    // 템플릿 명세는 `MakeEat<Squirrel>`
    // 함수 서명은 `void (Squirrel&)`
    // 함수 템플릿 인스턴스는 `MakeEat<Squirrel>(Squirrel&)`
    MakeEat(squirrel);

    // (2) 런타임 오류! 클래스 `Moai`에는 멤버 함수 `Eat`가 없습니다.
    // `T`는 Moai
    // 템플릿 명세는 `MakeEat<Moai>`
    // 함수 서명은 `void (Moai&)`
    // 함수 템플릿 인스턴스는 `MakeEat<Moai>(Moai&)`
    MakeEat(moai);
}
상기 코드는 `MakeEat`을 사용해서 실체화가 된 예제다. 다람쥐와 모아이 인스턴스를 인자로 전달했는데, 이때 함수 템플릿이 실체화한다. 그 결과 다람쥐는 멤버 함수 `Eat`를 가지고 있으므로 문제가 없지만, 모아이는 그렇지 않으므로 컴파일 오류가 발생한다. 첫번째로 알 수 있는 사실은 템플릿이 실제로 쓰이기 전에는, 모든 자료형에 대하여 통용되는 코드가 아니더라도 오류가 일어나지 않는다.

16.2. 예제2: 멤버 자료형의 존재 여부

#!syntax cpp
template<typename T>
void MakeIdle(T& statue)
{
    statue.Idle():
}
이번엔 가만히 있는 멤버 함수를 실행시키고자 한다. 그런데 만약 이 함수가 돌인 개체만 인자로 받고자 한다면 어떻게 할까? C++17에 따르면 if constexprstd::is_same_v<T, U>를 써서 돌덩이인지 아닌지를 검사할 수 있다. 하지만 SFINAE를 써보자.
#!syntax cpp
class Squirrel
{
public:
    void Idle() noexcept {};
    void Eat() noexcept {};
};

class Moai
{
public:
    using tag_is_stone = int;

    void Idle() noexcept {};
};
모아이는 석상이니까 덩어리다. 이를 모아이 클래스에 표시놓고 싶은데, static constexpr bool isStone = false;처럼 할 수 있겠지만 SFINAE를 활용해보자. 상기 코드에서는 모아이에 tag_is_stone의 이름으로 멤버 자료형 별칭을 선언했다. 식별자에서 알 수 있듯 일종의 태그, 구분자, 꼬리표 역할을 하는 멤버 자료형이다. 이 자료형 별칭은 존재 자체가 중요하며 어떤 자료형의 별칭인지는 상관없다. 다만 void면 안되고 상수일 수 있는 자료형이어야 한다.
tag_is_stoneint를 가리키므로 컴파일 시간에 값을 가질 수 있다. 곧 템플릿 매개변수에 값으로 넣을 수 있다. 무슨 말인지 모르니까 예제를 보자.
#!syntax cpp
// `T::will_stand_tag`가 쓰인 부분은 상수 템플릿 매개변수임.
template<typename T, T::tag_is_stone = 0>
void MakeIdle(T& statue)
{
    statue.Idle():
}
이 예제에서는 상수 템플릿 매개변수에 태그를 넣은 예제다. 이 함수 템플릿에서는 두번째 상수 매개변수에서 자료형에 속한 멤버 자료형을 읽어 들인다. 그런데 다람쥐의 경우 `tag_is_stone`을 멤버로 갖고 있지 않다. 즉, 이 함수에는 모아이만 인자로 전달할 수 있다.
#!syntax cpp
int main()
{
    Squirrel squirrel{};
    Moai moai{};

    // (3) 컴파일 오류! 인수 목록이 일치하는 함수 템플릿의 인스턴스가 없습니다.
    MakeIdle(squirrel);

    // (4) 클래스 `Moai`는 멤버 자료형 `tag_is_stone`를 가지고 있으므로 문제없이 실행됨.
    // `T`는 Moai
    // 템플릿 명세는 `MakeIdle<Moai, Moai::tag_is_stone>`
    // 함수 서명은 `void (Moai&)`
    // 함수 템플릿 인스턴스는 `MakeIdle<Moai, 0>(Moai&)`
    MakeIdle(moai);
}
상기 코드는 실체화한 예시를 보여주고 있다. 특기할만한 점은 다람쥐를 넣은 사례에서 아예 함수가 구현되지도 않았다고 오류를 띄우는 것이다. 이전의 예제에서는 생성자가 없다고 간접적으로 알 수 있었으나 이 사례에서는 오버로딩도 만들지 않는다. 두번째로 알 수 있는 사실은, 템플릿 명세에 SFINAE를 적용한 경우 아예 함수가 생성되지도 않는다는 것이다.

16.3. 예제3: 메타 데이터

이번에는 다람쥐와 모아이의 가장 큰 차이점인 생명체인가 아닌가를 표시해두고 싶다. 이전에 했던대로 태그를 쓸 수도 있겠으나 이번에는 다른 방법을 소개하고자 한다.
#!syntax cpp
class Squirrel
{
public:
    static constexpr bool isAlive = true;

    void Idle() noexcept {};
    void Breath() noexcept {};
};

class Moai
{
public:
    static constexpr bool isAlive = false;

    void Idle() noexcept {};
};
컴파일 상수를 써서 자료형에 직접 정보를 명시할 수 있다. 이를 메타 데이터(Meta Data)라고 한다. 자료형에 대한 정보를 기술하는 데이터이다.
#!syntax cpp
template<typename T>
[[nodiscard]]
constexpr bool IsAlive(const T& entity) noexcept
{
    return T::isAlive;
}

// 어차피 컴파일 상수를 써야 한다면 이쪽이 더 좋다.
template<typename T>
[[nodiscard]]
consteval bool IsAlive() noexcept
{
    return T::isAlive;
}
이렇게 만든 메타 데이터를 읽어서 자료형의 특징을 판별할 수도 있다.
그런데 이 방식은 의외의 단점이 있다. 왜냐하면 이 방식은 사용자가 클래스를 직접 수정해야 사용할 수 있다는 것이다. 즉 정보를 기입해놓지 않으면 적용할 수 없다. 예를 들어서,
#!syntax cpp
IsAlive<int>();
IsAlive(0.0f);
처럼 원시 자료형에는 적용할 수 없다. 어차피 컴파일 오류가 뜰 텐데 그냥 두어도 괜찮지 않나 싶지만 이 방식보다 그냥 함수 오버로딩을 하는게 더 쉽고 알아보기도 쉽다는 게 문제다. 요컨데 함수 오버로딩이 있는데 메타 데이터를 쓸 이유가 없다. C++에는 원시 자료형에 어떤 조작을 가할 방법이 없다. 그래서 굳이 써야 한다면 다른 방법을 써야 한다.

16.4. 예제4: 자료형 트레잇 (Trait)

#!syntax cpp
import <type_traits>;

class Squirrel
{
public:
    struct Nest {};

    void Idle() noexcept {};
    void Eat() noexcept {};
};

class Moai
{
public:
    void Idle() noexcept {};
};

template<typename T, typename = void>
struct SleepTraits : std::false_type {};

// C++17부터 사용 가능한 void_t
// SFINAE 사용
// 자료형 `T`가 멤버 자료형 `Nest`를 가지고 있으면 인스턴스화됨.
template<typename T>
struct SleepTraits<T, std::void_t<typename T::Nest>> : std::true_type {};

int main()
{
    Squirrel squirrel{};
    Moai moai{};

    // (5) `squirrel_has_home`의 값은 true
    constexpr bool squirrel_has_home = SleepTraits<Squirrel>::value;

    // (6) `moai_has_home`의 값은 false
    constexpr bool moai_has_home = SleepTraits<Moai>::value;

    // (7) `int_has_home`의 값은 false
    constexpr bool int_has_home = SleepTraits<int>::value;
}
상기 코드는 SFINAE를 통해 컴파일 시간에 클래스의 속성을 읽는 예제다. 자료형에 속한 멤버 자료형 별칭을 검사한다는 점에선 앞선 예제와 차이가 없어 보이나, 중요한 점은 기본 값을 정해줄 수 있고, 모든 자료형에 적용할 수 있다는 점이다. 상기 예제에서는 `SleepTraits`라는 컴파일 시간에 클래스의 속성을 검사하는 유틸리티 클래스를 만들었다. 이 `SleepTraits`는 기본적으로 표준 라이브러리의 std::false_type을 상속받는다. 여기서 Squirrel 클래스에 대하여 템플릿 특수화를 써서 표준 라이브러리의 std::true_type을 상속받게 만들었다. 그래서 `SleepTraits`에 다람쥐 클래스를 전달하면 상수값 true를 얻을 수 있다.

여기에 쓰인 std::true_typeC++11 std::false_typeC++11 은 컴파일 시간에 bool 값을 표현하는 수단으로써, 상수 템플릿 매개변수 대신 사용하기 위해 제정되었다. 표준 라이브러리에서는 <type_traits> 모듈에 존재한다. 대략적인 구현은 다음과 같다:
#!syntax cpp
namespace std
{
// 컴파일 시간에 값을 저장함.
template<typename T, T cv>
struct integral_constant
{
    static constexpr bool value = cv;
};

// bool에 대하여 특수화한 경우를 상속함.
template<bool cv>
struct bool_constant : integral_constant<bool, cv> {};

// bool의 참/거짓에 대하여 특수화함.
struct true_type : bool_constant<true> {};
struct false_type : bool_constant<false> {};
}
각각 컴파일 시간에 true, false의 값을 메타 데이터로 가지고 있다. 그냥 상수 템플릿 매개변수를 쓰지 왜 이런걸 쓰는가 하냐면, 이걸 쓸 수 밖에 없는 상황도 있기 때문이다. 이에 대해서는 이후 문단에서 설명한다.

17. 메타 함수

[include(틀:C++ 요소, head_keyword=template,
template_p0=템플릿-매개변수1, template_p1=템플릿-매개변수2,
template_last_label=...)]
#!if head_comment!=null
{{{#57A64A,#5EBD46 }}}
#!if head_lnb!=null
##======================================= include and import
[br]
#!if import!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{import }}}}}}'''{{{#C8865E {{{<>}}}}}}{{{;}}}
#!if include!=null
##======================================= the keyword at head (can be `template`)
{{{#include <>}}}
#!if (head_keyword_available = head_keyword != null)
##======================================= template parameters begin
'''{{{#DodgerBlue,#CornFlowerBlue {{{struct}}}}}}'''
#!if template_available = (template_p0 != null || template_v0 != null || template_p1 != null || template_v1 != null || template_p2 != null || template_v2 != null || template_p3 != null || template_v3 != null)
#!if template_available || template_last_label != null
##======================================= template parameter 0
##======================================= template parameter 0 concept
{{{<}}}{{{#!if template_concept0_available = (template_cpt0 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept0_params_available = (template_concept0_p0 != null || template_concept0_p1 != null || template_concept0_p2 != null || template_concept0_p3 != null)
{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{<}}}}}}{{{#!if template_concept0_params_available
##======================================= template parameter 0 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p0 != null || template_concept0_v0 != null) && (template_concept0_p1 != null || template_concept0_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p1 != null || template_concept0_v1 != null) && (template_concept0_p2 != null || template_concept0_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p2 != null || template_concept0_v2 != null) && (template_concept0_p3 != null || template_concept0_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept0_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept0_available
{{{ }}}}}}{{{#!if template_p0_available = (template_p0 != null)
{{{#!if !template_concept0_available
##======================================= template parameter 0 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v0_available = (template_v0 != null)
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}
##======================================= template parameter 0 finished
}}}{{{#!if (template_p0_available || template_v0_available) && (template_p1 != null || template_v1 != null)
##======================================= template parameter 1
{{{, }}}}}}{{{#!if template_concept1_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept1_params_available = (template_concept1_p0 != null || template_concept1_p1 != null || template_concept1_p2 != null || template_concept1_p3 != null)
{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{<}}}}}}{{{#!if template_concept1_params_available
##======================================= template parameter 1 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p0 != null || template_concept1_v0 != null) && (template_concept1_p1 != null || template_concept1_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p1 != null || template_concept1_v1 != null) && (template_concept1_p2 != null || template_concept1_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p2 != null || template_concept1_v2 != null) && (template_concept1_p3 != null || template_concept1_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept1_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept1_available
{{{ }}}}}}{{{#!if template_p1_available = (template_p1 != null)
{{{#!if !template_concept1_available
##======================================= template parameter 1 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}}}}{{{#!if template_v1_available = (template_v1 != null)
##======================================= template parameter 1 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if (template_p1_available || template_v1_available) && (template_p2 != null || template_v2 != null)
##======================================= template parameter 2
{{{, }}}}}}{{{#!if template_concept2_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept2_params_available = (template_concept2_p0 != null || template_concept2_p1 != null || template_concept2_p2 != null || template_concept2_p3 != null)
{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{<}}}}}}{{{#!if template_concept2_params_available
##======================================= template parameter 2 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p0 != null || template_concept2_v0 != null) && (template_concept2_p1 != null || template_concept2_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p1 != null || template_concept2_v1 != null) && (template_concept2_p2 != null || template_concept2_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p2 != null || template_concept2_v2 != null) && (template_concept2_p3 != null || template_concept2_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept2_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept2_available
{{{ }}}}}}{{{#!if template_p2_available = (template_p2 != null)
{{{#!if !template_concept2_available
##======================================= template parameter 2 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v2_available = (template_v2 != null)
##======================================= template parameter 2 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if template_available && template_last_label != null
##======================================= template finished
{{{, }}}}}}{{{#!if template_last_label != null
{{{...}}}}}}{{{#!if template_available || template_last_label != null
{{{>}}}}}}{{{#!if do_template_linebreak = (template_lnb != null)
##======================================= template linebreak
[br]}}}{{{#!if do_template_linebreak ;nbsp
}}}
#!if head_keyword_available!=null
{{{ }}}
#!if fn_attribute!=null
##======================================= contents
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr1]]}}}}}}]] 
#!if fn_attribute_lnk!=null
[[C++/문법/특성#attr1|{{{#a8a8a8 {{{[[attr1]]}}}}}}]] 
#!if fn_attribute_lnb!=null
[br]
#!if fn_attribute_lnb!=null ;nbsp
#!if kw1!=null
'''{{{#569cd6,#CornFlowerBlue {{{contexpr}}}}}}'''{{{#!if kw1_post!=null
{{{_kwp1}}}}}}{{{#!if kw1_post==null
{{{ }}}}}}
#!if kw2!=null
'''{{{#569cd6,#CornFlowerBlue {{{long long}}}}}}'''{{{#!if kw2post!=null
{{{&&}}}}}}{{{#!if kw2_post==null
{{{ }}}}}}
#!if cls_attribute!=null
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr2]]}}}}}}]]{{{ }}}
#!if cls_attribute_lnk!=null
[[C++/문법/특성#attr2|{{{#a8a8a8 {{{[[attr2]] }}}}}}]]
#!if ns1!=null
##======================================= namespaces
'''{{{#58fafe {{{std}}}}}}'''
#!if ns2!=null
{{{::}}}'''{{{#58fafe {{{chrono}}}}}}'''
#!if ns1!=null
{{{::}}}
#!if pre1_t!=null
##======================================= types at the front
{{{#4ec9b0,#6fdbba {{{메타-함수-식별자 }}}}}}
#!if pre2_t!=null
{{{::}}}{{{#4ec9b0,#6fdbba {{{duration }}}}}}
#!if pre_e!=null
{{{::}}}{{{#f0f068 {{{enum }}}}}}
#!if pre_post!=null
{{{_pre}}}
#!if pre_lnb!=null
[br] 
#!if do_pre_linebreak = (pre_lnb != null) &nbsp;
##======================================= pre-body things finished
#!if (!do_pre_linebreak && !do_template_linebreak && head_keyword_available) && (body_v != null || body_f != null || body_mv != null || body_mf != null || body_post != null)
{{{ }}}
#!if body_v!=null
##======================================= identifiers of variable and function
{{{#a8a8a8,#a1a1a1 {{{val}}}}}}
#!if body_mv!=null
{{{#ffffff {{{mem_val}}}}}}
#!if body_f!=null
{{{#f87a7a {{{fun}}}}}}
#!if body_mf!=null
{{{#f0a962 {{{mem_fn}}}}}}
#!if body_post!=null
##======================================= argument 1
{{{}}}
#!if body_tmpopen!=null
{{{<}}}
#!if body_bopen!=null
{{{(}}}
#!if arg1_concept!=null
'''{{{#4ec9b0,#6fdbba {{{concept1}}}}}}'''{{{#!if arg1_concept_params!=null
{{{<}}}{{{#!if arg1_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg1_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg1_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg1_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg1_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg1_t!=null
{{{#4ec9b0,#6fdbba {{{type1}}}}}}
#!if arg1_t_post!=null
{{{&}}}
#!if arg1_param != null && (arg1_kw != null || arg1_t_kw != null || arg1_t != null)
{{{ }}}
#!if arg1_param!=null
{{{#bcdce6 {{{param1}}}}}}
#!if arg2_concept != null || arg2_kw != null || arg2_t_kw != null || arg2_t != null || arg2_param != null
{{{, }}}
#!if arg2_concept!=null
##======================================= argument 2
'''{{{#4ec9b0,#6fdbba {{{concept2}}}}}}'''{{{#!if arg2_concept_params!=null
{{{<}}}{{{#!if arg2_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg2_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg2_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg2_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg2_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg2_t!=null
{{{#4ec9b0,#6fdbba {{{type2}}}}}}
#!if arg2_t_post!=null
{{{&}}}
#!if arg2_param!=null && (arg2_kw != null || arg2_t_kw != null || arg2_t != null)
{{{ }}}
#!if arg2_param!=null
{{{#bcdce6 {{{param2}}}}}}
#!if arg3_concept != null || arg3_kw != null || arg3_t_kw != null || arg3_t != null || arg3_param != null
{{{, }}}
#!if arg3_concept!=null
##======================================= argument 3
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg3_concept_params!=null
{{{<}}}{{{#!if arg3_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg3_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg3_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg3_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg3_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg3_t!=null
{{{#4ec9b0,#6fdbba {{{type3}}}}}}
#!if arg3_t_post!=null
{{{&}}}
#!if arg3_param!=null && (arg3_kw != null || arg3_t_kw != null || arg3_t != null)
{{{ }}}
#!if arg3_param!=null
{{{#bcdce6 {{{param3}}}}}}
#!if arg4_concept != null || arg4_kw != null || arg4_t_kw != null || arg4_t != null || arg4_param != null
{{{, }}}
#!if arg4_concept!=null
##======================================= argument4
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg4_concept_params!=null
{{{<}}}{{{#!if arg4_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg4_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg4_concept_tparam4!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}} 
#!if arg4_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg4_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg4_t!=null
{{{#4ec9b0,#6fdbba {{{type4}}}}}}
#!if arg4_t_post!=null
{{{&}}}
#!if arg4_param!=null && (arg4_kw != null || arg4_t_kw != null || arg4_t != null)
{{{ }}}
#!if arg4_param!=null
{{{#bcdce6 {{{param4}}}}}}
#!if arg5_param!=null
##======================================= argument5, argument6
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg6_param != null
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg_last_dots != null
{{{, ...}}}
#!if body_bclose!=null
##=======================================
{{{)}}}
#!if body_lnb!=null
[br] 
#!if body_lnb!=null
{{{ }}}
#!if body_spec1!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{ const}}}}}}'''
#!if body_spec1_paren != null
{{{(}}}
#!if body_spec1_ref!=null
{{{&}}}
#!if body_spec2!=null
{{{#!if body_spec1!=null && body_spec1_paren == null
{{{ }}}}}}'''{{{#DodgerBlue,#CornFlowerBlue {{{noexcept}}}}}}'''
#!if body_spec2_paren != null
{{{(}}}
#!if body_spec2_label != null
{{{...}}}
#!if body_spec2_paren != null
{{{)}}}
#!if body_spec1_paren != null
{{{)}}}
#!if body_tmpclose!=null
##======================================= last labels
{{{>}}}
#!if label_last!=null
{{{}}}
#!if last!=null
{{{;}}}


자료형에 대한 연산만을 위한 변수 템플릿 혹은 클래스 템플릿을 메타 함수 라고 한다. 이미 앞선 예제에서 몇가지 살펴본 바 있다. 표준 라이브러리의 경우는 메타 프로그래밍 문서에서 그 목록을 확인할 수 있다. 보통 클래스 템플릿은 자료형 트레잇 (Type Trait)을 통해 구현한다. 자료형 관습이라고 직역할 수 있는 자료형 트레잇은 자료형에 대한 속성을 받아 전달하거나, 두개 이상의 자료형에 대한 공통 속성을 저장하는 클래스다. 표준의 자료형 트레잇은 전부 다 정적 데이터 멤버와 자료형 별칭만 갖고 있다. 때문에 바이트 크기는 1이고, 자명한 자료형이다.

정의는 구구절절하게 설명했지만 결론은 메타 함수나 보통의 함수나 적용하는 곳이 다를 뿐, 근본적으로 튜링 완전하며 함수와 똑같은 행위를 할 수 있다는 것이다. 다른 점은 이렇게 전달받은 정보를 순수하게 템플릿 안에서만 조작한다는 점이다.

18. 가변 템플릿

[include(틀:C++ 요소, head_keyword=template,
template_p0=매개변수1, template_p0_post=...,
template_p1=매개변수2, template_p1_post=...,
template_last_label=..., last=;)]

[include(틀:C++ 요소, head_keyword=template,
template_v0_ty=매개변수2-자료형, template_v0=매개변수1, template_p0_post=...,
template_v1_ty=매개변수2-자료형, template_v1=매개변수2, template_p1_post=...,
template_last_label=..., last=;)]

[include(틀:C++ 요소, head_keyword=template,
template_p0=매개변수1, template_p0_post=...,
template_v1_ty=매개변수2-자료형, template_v1=매개변수2, template_p1_post=...,
template_last_label=..., last=;)]
가변 템플릿 (Variadic Template)C++11
C++11부터 임의의 수의 템플릿 매개변수를 전달할 수 있는 기능이 생겼다. 공식 명칭은 아니지만 가변 템플릿이라고 부르는 이 기능은 ...를 써서 작성할 수 있다. 자료형 매개변수의 typename뒤에 ...을 붙이거나, 상수 매개변수의 자료형 뒤에 ...을 붙일 수 있다.

이 기능은 배우기 까다롭지만 기능의 혁신성을 따지자면 C++11에서 열 손가락 안에 든다고 감히 말할 수 있다. 템플릿을 적어도 양적으로 무한하게 확장시킬 수 있기 때문이다. 예를 들어서 사용자가 데이터를 관리하기 위에 메모리나 구조체 여러개를 정의하고 썼던 것을 템플릿을 통해 필요한 데이터의 크기를 구하거나 메모리를 할당하는 데 필요한 작업을 미리 할 수 있다. 다른 예로는 네트워크 통신을 위해 패킷을 쓸 때, 기존에는 동적할당으로 어떻게 되든간에 일단 큰 메모리 공간을 할당해놓고 쓰는게 최선이었다. 패킷에 기록할 데이터의 구성을 알고 있더라도, 자료형의 크기를 전부 더하는 노가다가 필요하거나, 미리 만든 구조체를 reinterpret_cast<char*> 따위로 마샬링을 하는 작업이 필요했다. 임의의 여러개의 자료형에 대한 정보를 컴파일 시점에 알 수 있으므로 최적화에도 도움이 된다. 가변 템플릿과 함께라면 아무리 많은 데이터가 있어도 데이터의 구성이나 자료형에 대한 정보를 컴파일 시간에 처리할 수 있다.

한편 우리가 지금까지 썼던 콘솔 출력 함수 std::print, std::println 역시 가변 인자 함수다. 출력 함수들은 다수의 인자를 받아 문자열 형식화 이후 출력한다.

이 기능은 설명을 보는 것보다 활용 사례를 보는 게 이해에 좋다. 그래도 먼저 간단한 명세를 본 다음에 예제를 보도록 하자.

18.1. 가변 함수 템플릿

#!syntax cpp
template<typename... Ts>
void Function();

// (1) 템플릿 매개변수 명시
// 템플릿 명세는 template<int&, volatile float, long, double&&>
// 함수 서명은 void()
// 함수 템플릿 인스턴스는 Function<int&, volatile float, long, double&&>()
Function<int&, volatile float, long, double&&>();
함수 템플릿에 다수의 자료형을 명시적으로 전달할 수 있다.
#!syntax cpp
template<typename... Ts>
void Function(Ts... args);

int value = 0;

// (2) 함수 템플릿 매개변수 연역
// 템플릿 명세는 template<const char*, double, unsigned long, int>
// 함수 서명은 void()
// 함수 템플릿 인스턴스는 Function<const char*, double, unsigned long>(const char*, double, unsigned long, int)
Function("Hello, world!", 6000, 4000.2000, 1000UL, value);
템플릿 매개변수를 함수 매개변수로써 사용해도 전달할 수 있다. 그런데 이때 부패 현상이 일어난다. 만약 완벽한 전달을 하면:
#!syntax cpp
template<typename... Ts>
void Function(Ts&&... args);

int value = 0;

// (3) 완벽한 매개변수 전달
// 템플릿 명세는 template<const (&char)[14], double&&, unsigned long&&, int&>
// 함수 서명은 void()
// 함수 템플릿 인스턴스는 Function<const (&char)[14], double&&, unsigned long&&, int&>(const (&char)[14], double&&, unsigned long&&, int&)
Function("Hello, world!", 6000, 4000.2000, 1000UL, value);
참조자는 typename... 사이에 넣으면 된다. 자료형 한정자lvalue도 마찬가지로 사용할 수 있다.
#!syntax cpp
template<typename... Ts>
void Function(Ts&&... args); // 완벽한 전달 수행

// (4) 템플릿 매개변수 명시
// 템플릿 명세는 template<std::string>
Function<std::string>("Next phrase");

// (5) 템플릿 매개변수 명시
// 템플릿 명세는 template<long&&, const float&, int&&>
Function<long, const float&>(3500LL, 1750, 2000);
템플릿 매개변수를 명시하면 인자가 static_cast<T>를 통해 형변환된다.
#!syntax cpp
// (6) 가능
// Ts...를 맨 뒤에 배치함.
template<typename U, typename... Ts>
void Function(Ts... args);

// (7) 가능
// Ts...를 맨 뒤에 배치함.
// U가 함수 매개변수에 쓰임.
template<typename U, typename... Ts>
void Function(U, Ts... args);

// (8) 가능
// U가 함수 매개변수에 쓰임.
template<typename... Ts, typename U>
void Function(U, Ts... args);

// (9) 가능
// Ts...를 맨 뒤에 배치함.
// U가 함수 매개변수에 쓰임.
// 함수 매개변수의 순서는 상관없이 쓰였다는 사실이 중요함.
template<typename U, typename... Ts>
void Function(Ts... args, U);

// (10) 가능
// U가 함수 매개변수에 쓰임.
// 함수 매개변수의 순서는 상관없이 쓰였다는 사실이 중요함.
template<typename... Ts, typename U>
void Function(Ts... args, U);

// (11) 불가능
// Ts...와 U를 구별할 수 없음.
template<typename... Ts, typename U>
void Function(Ts... args);
가변 템플릿에서 템플릿 매개변수 묶음은 반드시 맨 뒤에 배치해야 한다. 그런데 앞서 말했듯이 함수 매개변수에 템플릿 매개변수가 쓰이면, 그 템플릿 매개변수는 template 안의 어느곳에든 배치할 수 있다. 이 사항은 가변 템플릿에도 적용된다.

18.2. 예제1: 가변 인자 함수

이 단락에서는 콘솔 출력 함수를 구현하는 방법을 알아보자. 이미 표준 라이브러리의 print 함수를 써왔지만 어떻게 돌아가는지 한번 보자. 참고로 print, println 등은 C언어fputs 또는 fprintf 따위의 고성능 함수로 구현되나, 먼저 템플릿의 기능을 보여주기 위해 std::iostream으로 구현할 것이다.
#!syntax cpp
// (1) `0` 출력
Print(0);

// (2) `Hello, world!` 출력
Print("Hello, world!");

// (3) 한줄 띄우기
Print('\n');

// (4) `1020304050` 출력
Print(10, 20, 30, 40, 50);

// (5) `This book has 40, pages.` 출력
Print("This book has ", 40, " pages.");

// (6) `argsN...`들의 값을 출력
Print(arg0, arg1, arg2, ...);
첫번째로 생각해볼 내용은 우리가 가변 인자 함수를 어떻게 쓸 것이냐다. 원하는 대로 인자를 전달하면 콘솔에 출력하는 간단한 기능을 가진다. 여기서 iostream<< 연산자나 기타 기능들은 안으로 숨길 생각이다.
#!syntax cpp
template<typename... Ts>
void Print(Ts... args);
그 다음은 함수의 서명을 생각해보자. 콘솔에 한번에 여러개의 값을 출력하려면 여러개의 함수 매개변수가 있어야 하고 템플릿 매개변수도 마찬가지로 여러개가 있어야 한다. 각 함수 매개변수는 템플릿 매개변수 원소 하나에 대응된다. 여기서 템플릿 매개변수 묶음 (Template Parameter Pack)을 명시해서 가변 인자 템플릿을 만들면, 이 함수는 가변 인자를 전달해서 실행할 수 있는 함수가 된다. 상기 코드에서는 매개변수 묶음 Ts...를 함수의 매개변수 args...에 사용한 예시를 보여주고 있다.
#!syntax cpp
Print(0, 1, 2, 3, 4);

Print(0, ", ", 1, ", ", 2, ", ", 3, ", ", 4);
구현하기 전에 인자를 넣어보자. 임의의 인자 기입이 가능함을 확인할 수 있다.
#!syntax cpp
template<>
void Print() {}
그리고 매개변수가 없는 경우를 특수화해보면 이 함수는 아무것도 안 할 것이다.
#!syntax cpp
template<typename... Ts>
void Print(const Ts&... args);
이 함수는 순수하게 인자의 값만 읽으므로 인자는 수정되지 않아야 하며 리터럴도 받을 수 있어야 한다. 상기 코드는 한정자 constlvalue 참조자를 함수의 매개변수에 부착했다. ...는 기재한 자료형 맨 뒤쪽에 붙여 기입한다.
#!syntax cpp
template<typename... Ts>
void Print(const Ts&... args)
{
    // 컴파일 오류! 템플릿 매개변수 팩을 확장해야 합니다.
    std::cout << args;
}
그럼 내용을 구현해보자. iostream을 써서 출력하므로 상기 코드와 같이 쓰면 될까? 정답은 아니요 다. 우리는 템플릿 매개변수 묶음 (Template Parameter Pack)을 써서 복수개의 템플릿 매개변수를 표현했다. 그럼 이 묶음을 풀어낼 수도 있어야 할 것이다. 우리는 여기에 ...를 기입해서 묶음을 풀어쓰겠다고 알려야 할 의무가 있다.
#!syntax cpp
template<typename... Ts>
void Print(const Ts&... args)
{
    // 컴파일 오류! 예상치 못한 토큰 ... 입니다.
    std::cout << args...;
    // 컴파일 오류! 예상치 못한 토큰 ... 입니다.
    (std::cout << args)...;
}
하지만 여전히 오류가 발생한다. 이유는 ...가 적용될 대상이 모호하기 때문이다. 괄호를 씌워줘도 마찬가지다.
#!syntax cpp
R function<Ts...>(Ts... args)
{
    ((SOMETHING_WITH_TEMPLATE<Ts>)...);
}
추후 설명하겠지만 이는 ...가 확장되는 방식과 관련이 있다. ...는 단순히 매개변수 여러개가 있다고 알려주는 문법 토큰이 아니다. 매개변수 묶음을 압축 해제하는 기능과 매개변수 묶음에 든 원소들을 순회하는 기능을 갖고 있다. 다시 말하자면 일종의 반복문을 템플릿을 통해 추상화한 형태다. ...는 매개변수 묶음의 식별자 `Ts`가 쓰인 어떤 장소에 있는 코드를, `Ts`에 담긴 원소의 개수만큼 반복 실행하는 목적을 가지고 있다.

템플릿 매개변수를 열거하는 방법은 두가지가 있다. 첫번째는 접힌 표현식(Fold expression), 두번째는 매개변수 묶음 확장이다. 처음은 접힌 표현식을 알아보자.

18.2.1. 접힌 표현식

접힌 표현식 (Fold Expression)C++17
접힌 표현식은 하나의 이항 연산자(Binary Operator)에 대하여 템플릿 매개변수를 열거하는 기능이다. 사용 방법은 다음과 같다:
  1. 원소 하나에 대한 코드를 작성한다.
  2. 어떤 이항 연산자[4]를 쓴다.
  3. ... (;)를 연산자 앞이나 뒤에 기입한다.
  4. 그 다음에 원소 하나의 코드를 소괄호로 감싸면, 소괄호로 감싼 부분에서 모든 템플릿 매개변수의 원소에 대응하는 코드를 생성해준다.
이게 무슨 말인지는 예제를 봐야 알 수 있다.
#!syntax cpp
template<typename... Ts>
auto Summary(Ts... args)
{
    return (args + ...);
}
상기 코드는 임의의 매개변수들의 합을 구하는 함수다. 실제로 이 함수에 숫자를 넣으면 잘 작동함을 알 수 있다. 이 코드를 분석해보자.
#!syntax cpp
// 반환형을 std::common_type_t<Ts...>로 대체할 수 있음.
template<typename... Ts>
auto Summary(Ts... args);
먼저 함수의 서명을 보자. 이 함수는 가변 함수 템플릿이며, 값에 의한 전달로 인자를 받는다. auto와 템플릿을 쓰므로 inline이라서 구현을 바로 해줘야 한다.
#!syntax cpp
return (args + ...);
그리고 내부의 반환식을 살며보자. 이 표현식은 앞서 얘기한 절차를 모두 따르고 있다.
#!syntax cpp
return args;
(1)번의 원소 하나에 대응하는 코드는 위와 같다. 사실 인자가 단 한개라면 return 뒤에 오는 항도 하나 밖에 없을 것이다.
#!syntax cpp
return args + 
(2)번대로 이항 연산자 +를 기입했다.
#!syntax cpp
return args + ...
(3)번대로 이항 연산자 뒤에 ...을 기입했다.
#!syntax cpp
return (args + ...);
(4)번대로 원소 하나에 대응하는 코드를 소괄호로 감쌌다. 이 함수는 첫번째 항 뒤에 연산자 +를 사이에 둔 매개변수가 일렬로 나열된 꼴을 가진다.
#!syntax cpp
Summary(50, 70, 90, 110, 130);

return (50 + 70 + 90 + 110 + 130);
인자가 대입되었다고 가정하고 풀어쓴 예시는 위와 같다.

18.2.2. 구현

#!syntax cpp
std::cout << value0 << value1 << ...;
<iostream>의 출력에서 쓰는 << 연산자도 이항 연산자다. 차이는 좌측항이 항상 std::cout로 고정되었다는 것이다.
#!syntax cpp
template<typename... Ts>
void Print(const Ts&... args)
{
    (std::cout << args << ...);
    (std::cout << args) << ...;
}
그래서 무턱대고 위와 같이 작성하면 오류가 발생한다. 처음에 말했다시피, 소괄호 안의 식을 반복한다고 말했었다.
#!syntax cpp
template<typename... Ts>
void Print(const Ts&... args)
{
    std::cout << (args << ...);
}
위와 같이 작성하면 `args...`의 원소들을 순회하면서 표현식 `(args << ...)`을 반복한다.

18.3. 예제2: 가변 인자 구조체

이 예제에서는 다수의 자료형을 담는 구조체를 구현하는 방법을 알아보자.

18.3.1. 순서쌍

순서쌍 (Ordered Pair)
이 구조체는 가변 인자 구조체는 아니지만 먼저 소개해야 이해를 돕기 위해 먼저 소개하겠다. 참고로 순서쌍 구조체는 이미 표준 라이브러리<utility> 모듈에 클래스 std::pair<T1, T2>로 구현되어 있으므로 참고할 것.

#!syntax cpp
template<typename T1, typename T2>
struct Pair
{
    T1 left;
    T2 right;
};
상기 코드는 두 자료형을 받아서 데이터 멤버로 저장하는 구조체 Pair를 정의하고 있다. 이 구조체는 암시적 기본 생성자, 암시적 소멸자, 암시적 복사/이동 생성자/대입 연산자를 가지고 있으며, 집결 초기화(Aggregate Initializaion)를 할 수 있다.

18.3.2. 튜플

튜플 (Tuple)
튜플은 다양한 자료형을 한번에 담을 수 있는 자료구조의 일종이다. 참고로 튜플 구조체는 이미 표준 라이브러리<tuple> 모듈에 클래스 std::tuple<T1, T2, ...>로 구현되어 있으므로 참고할 것.

#!syntax cpp
template<typename... Ts>
struct Tuple;
먼저 가변 인자 구조체가 어떤 형태인지 생각해보자. 상기 코드는 두 자료형을 받아서 데이터 멤버로 저장하는 구조체 Tuple를 선언하고 있다. 여기까진 좋다. 이제 데이터 멤버로 받은 모든 자료형에 대하여 데이터 멤버로 선언해야 한다. 그러면 Ts...를 풀어서 데이터 멤버의 자료형 자리에 기입하면 될까? 그런데 C++에는 그런 기능이 없다. C++26에서 Ts...[N]처럼 자료형 매개변수 묶음(Template Parameter Pack)을 배열처럼 쓸 수 있는 문법이 추가되었으나 아직 컴파일러들이 지원하는 날은 까마득하게 멀기만 하다.
#!syntax cpp
template<typename... Ts>
struct Tuple
{
    Ts... myValue;
};
예컨데 이게 안된다는 뜻이다. 그럼 해결 방법이 무엇일까? 결론부터 말하자면 템플릿 특수화를 이용해서 템플릿 매개변수 분할을 해야 한다. 이게 도대체 무슨 뜻인지 알아보자. 템플릿 특수화는 특정한 명확한 자료형이 들어온 상황에 어떤 동작을 할지 직접 코드로 쓰는 것이다. 그럼 간단한 상황부터 알아보자.
#!syntax cpp
template<typename... Ts>
struct Tuple; // 선언만 해놓음.

template<>
struct Tuple<> {}; // 아무런 데이터 멤버가 없다.

template<typename T>
struct Tuple<T>
{
    // 데이터 멤버가 하나 뿐이다.
    T myValue;
};

template<typename T1, typename T2>
struct Tuple<T1, T2>
{
    // 이 경우 순서쌍(Pair)과 똑같다.
    T1 myValue1;
    T2 myValue2;
};

// 이대로 특수화를 계속해야 할까?
template<typename T1, typename T2, typename T3, typename... Ts>
struct Tuple<T1, T2, T3, Ts...>;
아무런 자료형 매개변수가 없으면 튜플은 텅 비어있다. 자료형 매개변수가 하나면 데이터 멤버도 한개다. 당연하게도. 자료형 매개변수가 두개면 데이터 멤버는 두개다. 집결 초기화도 할 수 있다. 그런데 이건 그저 노가다일 뿐이다. 게다가 코드 읽기도 귀찮아진다. 데이터 멤버 이름도 전부 다르게 기입해야 한다. 그리고 튜플에서 데이터 멤버를 읽는 유틸리티 함수가 있으면 그것도 오버로딩을 따로 해줘야 한다. 정녕 방법이 이런 노가다 뿐일까?

<C++ 예제 보기>
#!syntax cpp
template<typename... Ts>
struct Tuple; // 선언만 해놓음.

template<>
struct Tuple<> {}; // 아무런 데이터 멤버가 없다.

template<typename T, typename... Ts>
struct Tuple<T, Ts...>
{
    T myValue;

    // (1) 컴파일 오류! 정적 데이터 멤버만 템플릿을 사용할 수 있습니다.
    template<typename... Ts>
    Tuple<Ts...> myChild;
};

// 편의성을 위한 추론 유도문
template<typename... Ts>
Tuple(Ts...) -> Tuple<Ts...>;

int main()
{
    // (2) `tuple0.myValue`의 값은 0
    // 한편 `tuple0`의 자료형은 추론 유도문에 의하여 `Tuple<int>`로 추론됨.
    constexpr Tuple tuple0{ 0 };

    // (3) 컴파일 오류! 이니셜라이저 값이 너무 많습니다.
    // 한편 `tuple1`의 자료형은 추론 유도문에 의하여 `Tuple<int, double>`로 추론됨.
    constexpr Tuple tuple1{ 0, 0.4 };

    // (4) 컴파일 오류! 이니셜라이저 값이 너무 많습니다.
    // 한편 `tuple2`의 자료형은 추론 유도문에 의하여 `Tuple<int, double, float, char>`로 추론됨.
    constexpr Tuple tuple2{ 0, 0.4, 0.2f, '4' };

  std::cout << "Hello World!\n";
}
상기 코드는 클래스 설계론에서 익히 들었을 포함 관계(Composite)를 동원해서 구현을 시도하는 예제다. 하지만 C++에서는 불가능하다. 이유는 템플릿 데이터 멤버는 반드시 정적(static)이어야 하므로 비정적인 하위 튜플 멤버는 템플릿일 수가 없다. 이러면 용도가 정적인 값만 쓸 수 있어서 매우 제한되므로 이 방법을 쓸 수 없다.

이제 우리의 뇌리 속에서 잊혀진 개념을 꺼내야 한다. C++에는 상속이라는 기능이 있다. 예제를 보기 전에 기작을 설명하겠다. 템플릿 데이터 멤버는 가질 수 없지만, 템플릿 매개변수를 자료형으로 하는 데이터 멤버는 가질 수 있다. 상속은 템플릿이 아닌 클래스가 템플릿 클래스를 상속받을 수 있다. 템플릿 클래스도 다른 템플릿 클래스를 상속받을 수 있다. 상속된 멤버는 상속된 순서대로 클래스 안에 정의된다. 상속된 클래스 사이에 멤버의 이름이 중복되어도 문제없다. 만약 생성자를 따로 정의하지 않았다면, 상속된 순서대로 기본 초기화 또는 집결 초기화를 사용할 수 있다. 포함 관계를 쓰는 방법은 아예 구현이 불가능하므로 상속을 써서 계속해보자.

<C++ 예제 보기>
#!syntax cpp
template<typename... Ts>
struct Tuple;

// 컴파일 오류! template<> 클래스가 정의되지 않았습니다.
template<typename T, typename... Ts>
struct Tuple<T, Ts...> : public Tuple<Ts...>
{
    T myValue;
};

template<typename... Ts>
Tuple(Ts...) -> Tuple<Ts...>;

int main()
{
    constexpr Tuple tuple0{ 0 };
    constexpr Tuple tuple1{ 0, 0.4 };
    constexpr Tuple tuple2{ 0, 0.4, 0.2f, '4' };
}
상기 코드는 상속을 이용해서 튜플을 정의하려고 시도하는 예제다. 그런데 아직 컴파일 오류가 발생한다. 왜냐하면 상속받을 때 Tuple<>처럼 템플릿이 비어있는 경우 데이터 멤버 `myValue`가 정의되지 않아서 문제가 생긴다.

<C++ 예제 보기>
#!syntax cpp
template<typename... Ts>
struct Tuple;

template<>
struct Tuple<> {}; // 아무런 데이터 멤버가 없다.

template<typename T, typename... Ts>
struct Tuple<T, Ts...> : public Tuple<Ts...>
{
    T myValue;
};

template<typename... Ts>
Tuple(Ts...) -> Tuple<Ts...>;

int main()
{
    // 컴파일 오류! 이니셜라이저 값이 너무 많습니다.
    constexpr Tuple tuple0{ 0 };
    constexpr Tuple tuple1{ 0, 0.4 };
    constexpr Tuple tuple2{ 0, 0.4, 0.2f, '4' };
}
그래서 템플릿 특수화를 써서 빈 구조체를 정의했는데 아직도 오류가 생긴다. 왜냐하면, Tuple<T, Ts...>로 특수화한 사례는 Ts...가 비어있지 않고 템플릿 매개변수가 있는 경우를 상정하기 때문이다. 그래서 매개변수가 하나인 경우도 특수화 해줘야만 한다.

#!syntax cpp
template<typename... Ts>
struct Tuple;

template<>
struct Tuple<> {};

template<typename T>
struct Tuple<T>
{
    T myValue;
};

template<typename T, typename... Ts>
struct Tuple<T, Ts...> : public Tuple<Ts...>
{
    T myValue;

    // Tuple<Ts...>의 멤버가 이 뒤로 나열된다.
};

template<typename... Ts>
Tuple(Ts...) -> Tuple<Ts...>;

int main()
{
    // (1) `tuple0`의 값은 `{ 0 }`
    // 정확하게는 `Tuple<int>{ 0 }`
    // `tuple0`의 자료형은 추론 유도문에 의하여 `Tuple<int>`로 추론됨.
    constexpr Tuple tuple0{ 0 };

    // (2) `tuple1`의 값은 { { 0.4 }, 0 }
    // 정확하게는 `Tuple<int, double>{ Tuple<double>{ 0.4 }, 0 }`
    // `tuple1`의 자료형은 추론 유도문에 의하여 `Tuple<int, double>`로 추론됨.
    constexpr Tuple tuple1{ 0, 0.4 };

    // (3) `tuple2`의 값은 { { { { '4' }, 0.2f }, 0.4 }, 0 }
    // 정확하게는 `Tuple<int, double. float, char>{ Tuple<double, float, char>{ Tuple<float, char>{ Tuple<char>{ '4' }, 0.2f },  0.4 }, 0 }`
    // `tuple2`의 자료형은 추론 유도문에 의하여 `Tuple<int, double, float, char>`로 추론됨.
    constexpr Tuple tuple2{ 0, 0.4, 0.2f, '4' };
}
이걸로 튜플 구조체는 완성되었다. 튜플에 온전히 값을 저장할 수 있다. 그런데, 저장한 값은 어떻게 읽어야 할까? 함수를 구현해서 읽으면 된다. 당연히 값을 읽고 싶으면 구조체의 데이터 멤버 `myValue`를 읽으면 된다. 그런데, 우리는 상속을 써서 튜플을 구현했다. 그리고, 모든 데이터 멤버의 이름은 `myValue`로 동일하다. 또 하나의 벽이 등장했다. 진정하고 생각해보자. 다행히도 C++은 상속받아도 같은 이름의 멤버를 구분하는 방법을 지원한다.

<C++ 예제 보기>
#!syntax cpp
int main()
{
    constexpr Tuple tuple{ 0, 0.4, 0.2f, '4' };

    // (1) `val0`의 값은 0
    constexpr auto val0 = tuple.myValue;

    // (2) 경고! 하나의 객체를 분할하지 마시오.
    // `val1`의 값은 (오류가 없으면) 0.4
    constexpr auto val1 = static_cast<Tuple<double, float, char>>(tuple).myValue;
}
그것은 바로 상위 클래스로 형변환하는 것이다. 하지만 이 동작은 표준에 따르면 비권장 동작이다. 만약 가상 클래스이거나, 메모리 바이트 정렬을 수정해야 하거나, 경고를 오류로 취급하는 환경에서는 쓸 수 없다. 또한 코드의 가독성, 호환성은 증발한다. auto의 도움도 못받고 자료형을 모조리 명시해야하기 때문이다. 템플릿의 장점이 무엇이었던가, 자료형을 숨길 수 있다는 것이다. 그럼 답이 없을까? 일단 함수의 개형을 생각해보자. 튜플의 특정 위치(숫자 번호) 또는 특정 자료형을 매개변수로 받아서 읽는 함수다.

<C++ 예제 보기>
#!syntax cpp
template<typename... Ts>
constexpr auto GetValueAt(const Tuple<Ts...>& tuple, const size_t index)
{
    // 컴파일 오류!
    // C++26에서는 튜플같은 집결 구조체에 대하여 [N] 연산을 지원하지만, `index`가 컴파일 상수가 아니라서 여전히 오류가 발생한다.
    return tuple[index];
}
아마도 튜플을 인자로 받아서 읽는 함수는 이런 느낌일 것이다. 하지만 불가능하다. 그럼 연산자 오버로딩은 어떨까?

<C++ 예제 보기>
#!syntax cpp
import <variant>; // std::monostate

template<>
struct Tuple<>
{
    [[nodiscard]]
    constexpr auto operator[](const size_t index) const
    {
        // ?
        return std::monostate{};
    }
};

template<typename T>
struct Tuple<T>
{
    T myValue;

    [[nodiscard]]
    constexpr auto operator[](const size_t index) const
    {
        if (0 == index)
        {
            return myValue;
        }
        else
        {
            // ?
            // 원하면 대신 예외를 발생시킬 수 있음.
            return myValue;
        }
    }
};

template<typename T, typename... Ts>
struct Tuple<T, Ts...> : public Tuple<Ts...>
{
    T myValue;

    [[nodiscard]]
    constexpr auto operator[](const size_t index) const
    {
        if (0 == index)
        {
            return myValue;
        }
        else
        {
            return static_cast<const Tuple<Ts...>&>(*this)[index - 1];
        }
    }
};
하지만 이래도 컴파일 오류가 발생한다. [] 연산자의 반환 자료형 auto를 컴파일러가 추론할 수 없기 때문이다. 이러면 결국 메타 프로그래밍을 동원해서 매개변수 묶음에서 원하는 자료형 하나를 분리해야 한다. 멤버 함수도 못 쓰고 전역 함수를 써야 한다. 상위 클래스로 형변환은 앞서 말했던 것 처럼 비권장 동작이기 때문이다. 메타 프로그래밍에서 쓰이는 값은 모두 컴파일 상수여야 한다. 그러므로 매개변수 묶음의 위치는 템플릿에 숫자를 전달하는 방식으로 넣어야 한다.

#!syntax cpp
template<size_t Index, typename... Ts>
constexpr auto GetValueAt(const Tuple<Ts...>& tuple)
{
    // ...
}
막막 하겠지만 템플릿 특수화를 동원하면 자료형에 대한 분할 정복을 시전할 수 있다.

<C++ 예제 보기>
#!syntax cpp
template<size_t Index, typename... Ts>
constexpr auto GetValueAt(const Tuple<Ts...>& tuple);

template<size_t Index, typename T>
constexpr auto GetValueAt(const Tuple<T>& tuple)
{
    if constexpr (Index == 0)
    {
        return tuple.myValue;
    }
    else
    {
        // 공란으로 두어도 괜찮다.
        // 이 경우 컴파일 오류가 발생한다. 숫자가 0이 아니면 비정상적인 상황이므로 오류가 발생하는 게 올바른 동작이다.
    }
}

template<size_t Index, typename T, typename... Ts>
constexpr auto GetValueAt(const Tuple<T, Ts...>& tuple)
{
    if constexpr (Index == 0)
    {
        return tuple.myValue;
    }
    else
    {
        return GetValueAt<Index - 1>(static_cast<const Tuple<Ts...>&>(tuple));
    }
}

int main()
{
    constexpr Tuple tuple0{ 1, 0.4, 0.2f, '4' };

    // (1) `val0`의 값은 52 (== '4')
    // `val0`의 자료형은 int
    constexpr auto val0 = GetValueAt<0>(tuple0);

    // (2) `val1`의 값은 0.2
    // `val1`의 자료형은 double
    constexpr auto val1 = GetValueAt<1>(tuple0);

    // (3) `val2`의 값은 0.4f
    // `val2`의 자료형은 float
    constexpr auto val2 = GetValueAt<2>(tuple0);

    // (3) `val3`의 값은 1
    // `val3`의 자료형은 char
    constexpr auto val3 = GetValueAt<3>(tuple0);

    // (4) 컴파일 오류! auto의 형식을 추론할 수 없습니다.
    constexpr auto val4 = GetValueAt<4>(tuple0);
}
하지만 순서가 반대로 나온다. 이번엔 튜플의 생성 방식이 문제다.

#!syntax cpp
template<typename T>
struct Tuple<T>
{
    using Super = void;

    T myValue;
};

template<typename T, typename... Ts>
struct Tuple<T, Ts...> : public Tuple<Ts...>
{
    using Super = Tuple<Ts...>;

    constexpr Tuple() = default;
    constexpr ~Tuple() = default;

    constexpr Tuple(const T& value, const Ts&... rests)
        : myValue(value), Super(rests...)
    {}

    T myValue;
};

int main()
{
    constexpr Tuple tuple0{ 1, 0.4, 0.2f, '4' };

    // (1) `val0`의 값은 1
    // `val0`의 자료형은 int
    constexpr auto val0 = GetValueAt<0>(tuple0);

    // (2) `val1`의 값은 0.4
    // `val1`의 자료형은 double
    constexpr auto val1 = GetValueAt<1>(tuple0);

    // (3) `val2`의 값은 0.2f
    // `val2`의 자료형은 float
    constexpr auto val2 = GetValueAt<2>(tuple0);

    // (3) `val3`의 값은 52 (== '4')
    // `val3`의 자료형은 char
    constexpr auto val3 = GetValueAt<3>(tuple0);
}
이와 같이 튜플에 생성자를 만들어주면 값의 순서가 정상적으로 나온다.

18.4. 예제3: 람다 표현식을 활용한 직렬화 함수

람다 표현식 문서에서 람다식을 정의한 즉시 사용하는 경우는 템플릿과 같이 활용하는 경우 몇몇을 빼면 없다고 말했었다. 이제 그 경우가 어떤 상황인지 알아보자.
{{{#!syntax cpp
char serializedBuffer[512];
}}}
지금부터 만들 함수는 튜플의 값을 모두 읽어서 하나의 큰 char 버퍼에 일렬로 쓰는 함수다. 이런 기능을 직렬화 (Serialization)라고 한다. 직렬화는 네트워크 통신, 압축 등 여러개의 데이터를 이동과 조작이 쉽도록 메모리 상에 쭉 놓이도록 변형하는 과정을 일컫는 용어다. 배열과도 비슷한데, 중요한 점은 모든 자료형에 대하여, 메모리 정렬이나 바이트 패딩과는 상관없이, 큰 메모리 덩어리를 만드는 것이다.

18.4.1. 개론: 직렬화에 대한 이해

#!syntax cpp
char buffer[512]{};

int value0 = 10;
int value1 = 20;
먼저 직렬화가 어떤 방식으로 구현되는지 알아보자. 상기 코드의 두 int를 하나의 버퍼에 쓰고자 한다.
#!syntax cpp
import <cstring>;
import <memory>;

int main()
{
    char buffer[512]{};

    int value0 = 10;
    int value1 = 20;

    // int의 바이트 크기는 보통 4다.
    memcpy(buffer, std::addressof(value0), 4);
    memcpy(buffer + 4, std::addressof(value1), 4);
    // buffer의 앞쪽 8바이트에 int 두개가 기록된다.
}
상기 코드는 표준 라이브러리의 메모리 복사 함수를 써서 버퍼에 기록한 예제다. 두개의 int를 연달아 버퍼에 쓰고 있다. 이 방식은 C언어에서도 유효하다.
#!syntax cpp
import <cstring>;
import <memory>;

int main()
{
    char buffer[512]{};

    long value0 = 10L;
    short value1 = 20;
    double value2 = 30.40;
    bool value3 = true;
    unsigned int value4 = 50U;

    // (1) long의 바이트 크기는 보통 4다.
    void* dest = reinterpret_cast<char*>(std::memcpy(buffer, std::addressof(value0), sizeof(long))) + sizeof(long);
    // (2) short의 바이트 크기는 보통 2다.
    dest = reinterpret_cast<char*>(std::memcpy(dest, std::addressof(value1), sizeof(short))) + sizeof(short);
    // (3) double의 바이트 크기는 보통 8이다.
    dest = reinterpret_cast<char*>(std::memcpy(dest, std::addressof(value2), sizeof(double))) + sizeof(double);
    // (4) bool의 바이트 크기는 보통 1이다.
    dest = reinterpret_cast<char*>(std::memcpy(dest, std::addressof(value3), sizeof(bool))) + sizeof(bool);
    // (5) unsigned int의 바이트 크기는 보통 4다.
    dest = reinterpret_cast<char*>(std::memcpy(dest, std::addressof(value4), sizeof(unsigned int))) + sizeof(unsigned int);
    // (6) buffer 앞쪽의 4+2+8+1+4, 총 19바이트가 쓰인다.
    // 직렬화 하기 전의 데이터의 총 크기는 메모리 정렬 상태에 따라 달라진다.
    // 만약 메모리 정렬이 최소 4바이트라면 4+4+8+4+4, 총 28바이트다.
}
만약 여러 종류의 자료형을 직렬화 한다면 최대한 일반화한 방식으로 하는 게 좋다. 상기 코드는 각기 다른 자료형을 같은 버퍼에 쓰는 예제다.

함수를 쓰지 않으니 좀 보기 힘든데 오버로딩을 써서 구현해보자. 우리가 값을 읽을 때는 상수면 충분하므로 상수 참조자를 쓰자.
#!syntax cpp
void* WriteToBuffer(void* buffer, const bool& value)
{
    return reinterpret_cast<char*>(std::memcpy(buffer, std::addressof(value), sizeof(bool))) + sizeof(bool);
}

void* WriteToBuffer(void* buffer, const int& value)
{
    return reinterpret_cast<char*>(std::memcpy(buffer, std::addressof(value), sizeof(int))) + sizeof(int);
}

// 기타 오버로딩 추가...
여기서 템플릿의 진가가 드러난다. 템플릿을 쓰면 간결하게 축약할 수 있다.
#!syntax cpp
template<typename T>
void* WriteToBuffer(void* buffer, const T& value)
{
    return reinterpret_cast<char*>(std::memcpy(buffer, std::addressof(value), sizeof(T))) + sizeof(T);
}

int main()
{
    char buffer[512]{};

    long value0 = 10L;
    short value1 = 20;
    double value2 = 30.40;
    bool value3 = true;
    unsigned int value4 = 50U;

    void* dest = WriteToBuffer(buffer, value0);
    dest = WriteToBuffer(dest, value1);
    dest = WriteToBuffer(dest, value2);
    dest = WriteToBuffer(dest, value3);
    WriteToBuffer(dest, value4);
}
훨씬 깔끔한 코드가 되었다. 하지만 아직 완전한 기능을 갖추진 못했다. 원시 자료형만 직렬화 할 수 있기 때문이다. 가령 표준 라이브러리의 <string> 문자열만 해도 그렇다.
#!syntax cpp
template<>
void* WriteToBuffer(void* buffer, const std::string& str)
{
    auto sz = str.length() * sizeof(char);
    return reinterpret_cast<char*>(std::memcpy(buffer, str.data(), sz)) + sz;
}

template<>
void* WriteToBuffer(void* buffer, const std::wstring& str)
{
    auto sz = str.length() * sizeof(wchar_t);
    return reinterpret_cast<char*>(std::memcpy(buffer, str.data(), sz)) + sz;
}
이는 템플릿 특수화로 해결할 수 있다. 그러나 이런 식이면 std::u32string 등 다른 문자열도 다 오버로딩해줘야 해서 귀찮아진다. std::string은 사실 std::basic_string<typename CharType, typename Trait>로 표현되는 클래스 템플릿 인스턴스다. 이걸 알면 오버로딩 한번에 해결할 수 있다.
#!syntax cpp
template<typename CharType, typename Trait>
void* WriteToBuffer(void* buffer, const std::basic_string<CharType, Trait>& str)
{
    auto sz = str.length() * sizeof(CharType);
    return reinterpret_cast<char*>(std::memcpy(buffer, str.data(), sz)) + sz;
}
이런 식으로 쭉 사용자 정의 클래스를 포함해서 모든 자료형을 직렬화할 수 있다.

이제 본격적으로 기능 확장을 해보자. 보다 안전한 코드가 되어야 하고, 성능 문제, 예외 사양도 해결하고 싶다. 다수의 값을 한번에 직렬화 하고도 싶다. 요구사항이 엄청 많은데 하나 하나 해보자. 먼저 안전한 코드란 포인터를 숨기며 메모리 오버플로우를 방지해야 한다.

#!syntax cpp
template<typename T, size_t BuffLen>
auto WriteToBuffer(char (&buffer)[BuffLen], const T& value);
먼저 포인터를 안으로 숨기고 명확한 버퍼의 크기를 매개변수에 명시하자.
#!syntax cpp
template<typename T, size_t BuffLen>
auto WriteToBuffer(char (&buffer)[BuffLen], const T& value)
{
    static_assert(sizeof(T) <= BuffLen);
}
버퍼의 크기는 자료형의 바이트 크기 보다 커야 할 것이다.
#!syntax cpp
template<typename T, size_t BuffLen>
auto WriteToBuffer(char (&buffer)[BuffLen], const T& value)
{
    static_assert(sizeof(T) <= BuffLen);

    auto err = memcpy_s(buffer, BuffLen, std::addressof(value), sizeof(T));
    if (0 != err)
    {
        // 메모리 문제 발생!
    }
}
이왕 바꾸는 거 안전한 버전의 함수를 쓰자.
#!syntax cpp
template<typename T, size_t BuffLen>
size_t
WriteToBuffer(char (&buffer)[BuffLen], const T& value)
noexcept(sizeof(T) <= BuffLen && std::is_nothrow_copy_constructible<T>)
{
    static_assert(sizeof(T) <= BuffLen);

    return 0 != memcpy_s(buffer, BuffLen, std::addressof(value), sizeof(T)) ? 0 : sizeof(T);
}
예외 사양도 달아주자. 그런데 이렇게 만드니 다 좋은데 함수 매개변수가 포인터가 아니라 여러번 직렬화할 수가 없다. 그리고 여러 개의 인자를 담지 못하는 문제가 있다. 첫번째 매개변수에 버퍼 포인터를 받는 오버로딩을 하면 되지 않나 싶지만, 그러면 그 오버로딩은 런타임에만 얻을 수 있는 정보 (현재 값을 쓰는 오프셋, 메모리 버퍼의 상태, 예외 사양 등)를 가지게 되면서 기껏 붙인 안전장치를 다시 떼는 일이 생긴다. 차선책은 여러가지가 있지만 이번에 진행할 내용은 사용자가 버퍼에 접근하는 횟수는 단 한번으로 잡고 다수의 인자를 한번에 직렬화하는 방안이다.

18.4.2. 서론: 매개변수 묶음 열거

#!syntax cpp
template<size_t BuffLen, typename... Ts>
auto WriteToBuffer(char (&buffer)[BuffLen], const Ts&... values)
{
    // Ts... 를 열거
}
그럼 가변 인자 함수 템플릿을 구현해야 한다. 그런데 여기엔 문제가 있는데, 아직 우리가 매개변수 묶음을 열거하는 방법을 알지 못한다는 것이다. 앞서 구현했던 튜플을 쓰면 어떨까?
#!syntax cpp
template<size_t BuffLen, typename... Ts>
auto WriteToBuffer(char (&buffer)[BuffLen], const Tuple<Ts...>& pack)
{
    // Ts... 를 열거
}
하지만 똑같은 상황에 처한다. 필요한 동작은 매개변수 여러개를 열거하는 것이고 튜플은 그저 데이터를 담는 용도에 그치기 때문이다. 여기서 잠시 논리를 생각해보자. 코드의 구성은 어떻게든 Ts...를 열거할 수 있다면 각각 성분에 대하여 직렬화를 반복하는 형태가 될 것이다. 즉 내부 구현에선 마치 반복문 처럼 매개변수 묶음을 순회하고, 반복적인 코드(직렬화)를 수행하는 형태가 된다.
#!syntax cpp
template<size_t BuffLen, typename... Ts>
auto WriteToBuffer(char (&buffer)[BuffLen], const Ts&... values)
{
    for (size_t I = 0; I < sizeof...(Ts); ++I)
    {
        using T = Ts...[I];
        _Serialize(buffer, values[I]);

        buffer += sizeof(T);
    }
}
아마 위와 같은 방식으로 구현할 수 있지 않을까? 마침 C++26에서 매개변수 묶음을 배열처럼 Pack[N]을 사용하는 문법이 추가되었으므로 조만간 가능하다는 기대를 할 수 있다. 하지만 우리는 지금 당장 구현을 해야 한다. 또한 C++26에서도 대괄호 안의 숫자는 컴파일 상수여야 한다. 상수 템플릿 매개변수이어야 한다는 말이다.
#!syntax cpp
template<size_t... Indices, size_t BuffLen, typename... Ts>
auto WriteToBuffer(char (&buffer)[BuffLen], const Ts&... values)
{
    using T = Ts...[Indices...];
    // ???
}

int main()
{
    char buffer[128]{};

    WriteToBuffer<0, 1, 2>(buffer, 10, 30, 50); // ?
}
점점 더 오리무중으로 빠진다. 일단 첫번째로 짚고 넘어가야 할 것은 사용자 코드에서의 실사례다. 사용자가 직렬화 함수를 쓸 때는 깔끔하게 버퍼와 직렬화할 값만 전달받아야 한다. 부수적이고 복잡한 요소는 안으로 숨겨야 한다. 즉 상수 템플릿 매개변수는 본 함수가 가져서는 안되고, 내부 구현 함수가 가져야 한다.
#!syntax cpp
template<size_t BuffLen, typename... Ts>
auto WriteToBuffer(char (&buffer)[BuffLen], const Ts&... values)
{
    WriteToBufferImpl<0, ... sizeof...(Ts)>(buffer, values...);
}

int main()
{
    char buffer[128]{};

    WriteToBuffer(buffer, 10, 30, 50);
}
상기 코드는 그럴 듯 해보이지만 슬프게도 C++에는 저렇게 수열을 표현하는 문법이 없다. 스크립트 언어 중에선 저런 식으로 숫자 범위를 만들 수 있는 언어가 많으나.. 그래도 정말 다행히도 상수 매개변수로 수열을 표현할 방법이 있다. 표준 라이브러리<utility> 모듈에서 integer_sequence<T, I...>를 비롯한 유틸리티 템플릿을 제공한다.
18.4.2.1. 표준 라이브러리: integer_sequence
#!syntax cpp
template<typename T, T... Constants>
struct [[nodiscard]] integer_sequence
{
    using value_type = T;

    [[nodiscard]]
    static constexpr std::size_t size() noexcept
    {
        return sizeof...(Constants);
    }
};
std::integer_sequenceC++14 는 상기 코드로 구현되는 클래스 템플릿이다. 이 클래스의 역할은 다수의 상수 템플릿 매개변수를 integer_sequence 인스턴스 하나로 한데 모으는 것이다. 이게 무슨 뜻일까?
#!syntax cpp
import <utility>;

template<typename T>
void Function(T sequence);

int main()
{
    Function(std::integer_sequence<int, 0, 2, 4, 7, 102, 9376, 6504, 210581>{});
    Function(std::integer_sequence<float>{});
    Function(std::integer_sequence<long long, 20, 1, -15938>{});
    Function(std::integer_sequence<char, 'H', 'e', 'l', 'l', 'o'>{});
}
와 같이 단일 템플릿 매개변수만 받는 함수에 복잡한 integer_sequence를 전달해도 하나의 자료형으로 축약된다. 또한 integer_sequence는 자체적인 데이터 멤버가 없으므로 바이트 크기도 1이며, 모든 것이 컴파일 시점에 결정되기에 최적화에 문제도 안 일으킨다. 당장 integer_sequence에 상수를 수백 수천개 집어 넣어도 런타임에는 아무런 영향이 없다.
#!syntax cpp
template<std::size_t... Indices>
using index_sequence = std::integer_sequence<std::size_t, Indices...>;

template<typename T, T Count>
using make_integer_sequence = std::integer_sequence<T, /* 0, 1, 2, ..., Count - 1 로 나타내어지는 수열 */>;

template<std::size_t Count>
using make_index_sequence = std::make_integer_sequence<std::size_t, Count>;

template<typename... Ts>
using index_sequence_for = std::make_index_sequence<sizeof...(Ts)>;
또한 도우미 유틸리티 템플릿도 다수 제공한다. 모두 integer_sequence의 자료형 별칭이다.
#!syntax cpp
// (1) `Seq0`은 std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>
using Seq0 = make_integer_sequence<int, 10>;

// (2) `Seq1`은 std::integer_sequence<std::size_t, 0, 1, 2, 3, 4>
using Seq1 = make_index_sequence<5>;

// (3) `Seq2`는 std::integer_sequence<std::size_t>
using Seq2 = make_index_sequence<0>;

// (4) `Seq3`은 std::make_index_sequence<3> == std::integer_sequence<std::size_t, 0, 1, 2>
using Seq3 = index_sequence_for<unsigned short, long, wchar_t>;

// (5) `Seq4`는 std::make_index_sequence<0> == std::integer_sequence<std::size_t>
using Seq4 = index_sequence_for<>;
상기 코드는 관련 유틸리티의 예시를 보여주고 있다. 중요한 사실은 매개변수가 없이 비어있는 템플릿의 경우 비어있는 대로 생성된다는 것이다.

18.4.3. 본론: 템플릿 매개변수 묶음 확장

이제 우리가 할 일은 내부 구현 함수에 모든 자료형과 자료형의 개수와 인덱스를 한번에 전달하는 것이다. 자료형은 튜플로, 인덱스는 std::integer_sequence로 전달하면 된다.
#!syntax cpp
import <tuple>;
import <utility>;

template<typename Tuple, std::size_t... Indices>
auto WriteToBufferImpl(char* ptr, Tuple&& tuple, std::index_sequence<Indices...>);

template<std::size_t BuffLen, typename... Ts>
auto WriteToBuffer(char(&buffer)[BuffLen], Ts&&... values)
{
    WriteToBufferImpl(buffer, std::forward_as_tuple(std::forward<Ts>(values)...), std::index_sequence_for<Ts...>{});
}
튜플은 하나의 매개변수로 압축해서 전달해도 된다. 왜냐하면 표준 라이브러리의 튜플에서 값을 읽는 함수 std::get<I>(tuple)은 내부적으로 튜플의 값을 찾는 로직이 구현되어 있으므로 문제가 없다. 상기 코드에서는 `WriteToBufferImpl`의 템플릿 매개변수 typename Tuplestd::tuple<Ts...>의 템플릿 매개변수 묶음을 숨기고 전달하고 있다. 그러나 아직 우리는 매개변수 묶음을 열거하는 방법을 모른다. 이럴 땐 정답을 미리 확인해보자.

#!syntax cpp
import <tuple>;
import <utility>;
import <memory>;

template<typename Tuple, std::size_t... Indices>
auto WriteToBufferImpl(char* ptr, Tuple&& tuple, std::index_sequence<Indices...>)
{
    // 앞선 예제에서 구현한 직렬화 함수와 같다.
    auto writer = []<typename T>(char* ptr, T&& value) -> std::size_t
    {
        memcpy(ptr, std::addressof(value), sizeof(T));

        return sizeof(T);
    };

    // 여기에서 템플릿 매개변수를 열거한다.
    // `Indices...`의 개수에 따라 코드가 반복되어 실행된다.
    // 이 모든 과정은 컴파일 시간에 코드의 동작이 결정된다!
    ((ptr += writer(ptr, std::get<Indices>(std::forward<Tuple>(tuple)))), ...);
}

template<std::size_t BuffLen, typename... Ts>
auto WriteToBuffer(char(&buffer)[BuffLen], Ts&&... values)
{
    WriteToBufferImpl(buffer, std::forward_as_tuple(std::forward<Ts>(values)...), std::index_sequence_for<Ts...>{});
}

int main()
{
    char buffer[512]{};
    WriteToBuffer(buffer, 10, 20, 40, 80);

    constexpr float v2 = 62491.13;
    WriteToBuffer(buffer, 0, v2);

    WriteToBuffer(buffer, 40160389150, true, 682.079318124);
}
디버그 모드에서 확인해보면 잘 작동하는 것을 확인할 수 있다. 이제 원리를 알아보자.

매개변수 묶음 확장 C++11
template<typename... Ts> Function(Ts... args);
Function(10L, 20, 30.0f)
template<typename... Ts>
Function(Ts... args)
template<long, int, float>
Function(long args, int args, float args)
상기한 예제는 매개변수 묶음 확장(Pack Expansion)이라는 문법을 써서 구현했다. 이 문법은 사실 처음부터 쓰고 있었다. 우리가 지금까지 가변 템플릿을 썼을 때 template<typename... Ts>와 같이 typename 앞에 ...를 썼다. 그리고 Function(Ts.... args)처럼 인자를 전달해왔다. 여기서 ...,(콤마)를 써서 성분을 쭉 나열한다는 뜻이다. 매개변수 묶음이 사용된 구간을 패턴 (Patern)이라고 한다. 매개변수 묶음과 ...이 감싸는 부분이 패턴으로서 콤마와 함께 반복된다는 것이다. 컴파일러가 알아서 묶음에 쓰인 다수의 템플릿 매개변수를 나열해준다. 예시를 들어보자.

#!syntax cpp
template<typename... Ts>
void Prints(Ts&&... values)
{
    std::print("{} ", std::forward<Ts>(values)...);
}

int main()
{
    Prints(10, 20L, 30U, 40);
}
같이 간단한 가변 출력 함수를 생각해보자. 여러개의 값을 출력하고 싶은데, std::print의 소괄호 안에서 매개변수 values...가 쓰이고 있으므로 소괄호 안에서 확장시키면 될까? 결론부터 말해서 이 함수는 작동하지 않는다.
#!syntax cpp
// 컴파일 오류!
std::print("{} ", 10, 20L, 30U, 40);
std::print의 첫번째 매개변수에는 문자열 의 출력 형식을 전달하는데, 이 사례처럼 "{}"을 전달하면 단 하나의 값만 출력하겠다는 의미다. 매개변수 묶음이 괄호 안에서 확장되면 괄호 안에 여러개의 값이 대입된다. 이 때문에 컴파일이 실패한다.

#!syntax cpp
template<typename... Ts>
void Prints(Ts&&... values)
{
    (std::print("{} ", std::forward<Ts>(values)), ...);
}

int main()
{
    Prints(10, 20L, 30U, 40);
}
이 함수는 인자를 몇개를 넣던 간에 잘 출력한다. 그런데 어떻게 동작하는 걸까? ...가 쓰인 곳의 패턴이 복사되어 모든 성분에 대하여 반복된다. 패턴이 어떤 것인지 알 수 있는 기준은 제일 안쪽의 항을 기준으로 잡고 패턴을 확인해야 한다.
#!syntax cpp
std::forward<Ts>(values)
완벽한 전달을 하는 이 문장이 패턴을 파악하는 구심점이 된다. 매개변수 묶음 values...이 여기로부터 확장된다. 다르게 표시해보자.
#!syntax cpp
std::print("{} ", FORWARD<Ts>)
이 문장은 출력하는 문장이다. 이 문장도 패턴이 된다.
#!syntax cpp
(PRINT<Ts>, ...);
여기서 Ts의 성분에 대하여 코드가 복사되어 나열된다.
#!syntax cpp
PRINT<int>(10), PRINT<long>(20L), PRINT<unsigned int>(30U), PRINT<int>(40);
예제에서 생성될 패턴은 위와 같이 나타날 것이다.
#!syntax cpp
std::print("{} ", 10), std::print("{} ", 20L), std::print("{} ", 30U), std::print("{} ", 40);
결론적으로 Prints 함수 템플릿에서 생성된 함수의 구현은 상기 코드와 같다.
#!syntax cpp
((PATERN<T>), ...);
실제로 구현할 때는 단 하나의 성분에 대해 실행 코드를 작성하고, 패턴이 되어 반복될 부분을 괄호로 감싼 다음, 바깥 항에 콤마와 ...를 붙여 작성할 수 있다.

18.4.4. 결론

{{{#!syntax cpp
((ptr += writer(ptr, std::get<Indices>(std::forward<Tuple>(tuple)))), ...);
}}}이제 다시 돌아가서 이 문장을 천천히 뜯어보자.
#!syntax cpp
std::forward<Tuple>(tuple)
첫번째로 이 표현식은 튜플을 전달하는 패턴으로서 매개변수 확장과는 관련이 없다. 임시로 문장을 가리자.
#!syntax cpp
((ptr += writer(ptr, std::get<Indices>(FORWARD_TUPLE))), ...);
와 같이 말이다.
#!syntax cpp
std::get<Indices>(FORWARD_TUPLE)
이 표현식은 튜플에서 값을 가져오는 패턴이다. 바로 이 문장이 Indices...가 확장되는 구심점이다.
#!syntax cpp
((ptr += writer(ptr, GET_VALUE_FROM_TUPLE<Indices>)), ...);
와 같이 표시해놓자.
#!syntax cpp
writer(ptr, GET_VALUE_FROM_TUPLE<Indices>)
이 표현식은 직렬화를 하는 패턴이다. 또한 람다 표현식 `writer`는 자료형의 크기를 반환한다.
#!syntax cpp
((ptr += WRITE_AT<Indices>), ...);
와 같이 표시해놓자.
#!syntax cpp
ptr += WRITE_AT<Indices>
이 표현식은 `writer`가 반환한 자료형의 크기를 버퍼 포인터 `ptr`에 더하는 패턴이다. 이 패턴이 있음으로써 버퍼 상에 연속적으로 값을 쓸 수 있다.
#!syntax cpp
(SERIALIZE_AT<Indices>, ...);
와 같이 표시해놓을 수 있다. 결국 이 패턴은 소괄호 안에 모든 직렬화 구문을 일렬로 쭉 기입한 모양이 된다. 가령,
#!syntax cpp
SERIALIZE_AT<0>, SERIALIZE_AT<1>, SERIALIZE_AT<2>, SERIALIZE_AT<3>;
와 같이 방식으로 한줄로 코드가 나열된다는 뜻이다.

18.5. 예제4: 가변 변수 템플릿

이제 직렬화 함수에 안전장치를 달 시간이다. static_assert로 버퍼의 크기에 따라 직렬화 할 수 있는 수를 제한하고, noexcept로 예외 사항을 정해보자. 먼저, 직렬화할 수 있는 수의 제한이라는 건 모든 자료형의 바이트 크기를 합한 숫자가 char배열 버퍼의 크기보다 작거나 같아야 한다는 것이다.
#!syntax cpp
template<typename... Ts>
constexpr std::size_t TotalByteSizeOf = (sizeof(Ts) + ...);

template<>
constexpr std::size_t TotalByteSizeOf<> = 0;
상기 코드로 다수의 자료형의 바이트 크기의 합을 구할 수 있다.

#!syntax cpp
template<typename Tuple, std::size_t... Indices>
auto WriteToBufferImpl(char* ptr, Tuple&& tuple, std::index_sequence<Indices...>);

template<std::size_t BuffLen, typename... Ts>
auto WriteToBuffer(char(&buffer)[BuffLen], Ts&&... values)
{
    // 컴파일 단계에서 버퍼의 크기와 바이트 크기의 합을 비교한다.
    static_assert(TotalByteSizeOf<Ts...> <= BuffLen);

    WriteToBufferImpl(buffer, std::forward_as_tuple(std::forward<Ts>(values)...), std::index_sequence_for<Ts...>{});
}
접힌 표현식을 통해 자료형들의 바이트 크기 합을 구했다. 이제 컴파일 시점에 버퍼 오버플로우를 검사한다. 다음은 예외 사양을 달아보자.

18.6. 예제5: 가변 자료형 트레잇으로 구현하는 메타 함수

예외 사양이란 함수에 noexcept(constexpr-bool)를 달아서 예외가 일어나는지 아닌지 여부를 표기하는 걸 말한다. 만약 직렬화를 수행할 때 문제가 없으면 noexcept(true)이고[5], 아주 티클이라도 오류가 일어날 가능성이 있다면 noexcept(false)[6]. 하지만 먼저 한가지만 짚고 넘어가야 한다. 직렬화 과정에 C언어memcpy가 쓰이는데, 사실 이 함수가 터질 위험성은 언제든지 존재한다. 하지만 일단 이 함수에 문제가 생겨도 오류를 터트리는 대신 그냥 nullptr를 반환하는 거에 그칠거라 믿고 넘어가자. 일단 이 함수가 터지는 경우는 운영체제 잘못이라고 생각하자. 우리가 직접 함수를 사용하는 부분엔 문제가 생기지 않도록 코드를 작성하는 데에 목적이 있으니 말이다.

memcpy는 메모리 내용을 그대로 본따서 복사하는 함수다. 이 함수에 문제가 없으려면 복사하는 값이 자명해야 하고, 복사 가능해야 한다. 정확하게 말하자면, 자명하게 복사 가능(Trivially Copyable)해야 한다. 또한, 자명하게 복사하는 과정에서 예외를 던지지 않아야 한다. 이 조건은 다행히도 포인터를 포함한 모든 원시 자료형이 만족한다.
#!syntax cpp
template<std::size_t BuffLen, typename... Ts>
auto WriteToBuffer(char(&buffer)[BuffLen], Ts&&... values) noexcept;
현재 우리가 새로이 구현한 직렬화 함수는 문자열도 못받고 원시 자료형만 상태이므로 당장은 noexcept만 딸랑 붙여도 문제는 없다. 그러나 확장성을 위하여 엄밀하게 정의해보자. 이번엔 트레잇을 구현할 예정이다. 추후에 구현할 함수 오버로딩까지 고려한 결정이다.
#!syntax cpp
template<typename... Ts>
struct MarshalTrait
{
    // 수정 예정
    static constexpr bool IsNothrowSerializable = false;
};

template<typename... Ts>
constexpr bool IsNothrowMarshallable = MarshalTrait<Ts...>::IsNothrowSerializable;
이 트레잇은 다양한 역할을 맡을 예정인데 일단 예외 검사만 수행하자.
#!syntax cpp
import <type_traits>;

template<typename T>
struct CheckNothrowMarshallableTrait : std::bool_constant<std::is_trivially_copy_constructible_v<T>>
{};
예외 검사 전용 트레잇을 만들어주자. 이 트레잇은 단일 자료형이 자명하고 예외없이 복사가능한지 검사한다. 이 트레잇에 원시 자료형을 전달하면 모두 true가 나온다. 우리는 noexcept 안에서 이 트레잇을 열거해야 한다. 기억할 점은 이 트레잇은 표준 라이브러리integral_constant<bool>을 상속받으며, 값은 value로 읽을 수 있다. 표준 라이브러리에는 이를 열거할 수 있는 메타 함수를 제공한다.

18.6.1. 표준 라이브러리: conjunction

[include(틀:C++ 요소, head_keyword=template,
template_p0=자료형-매개변수1,
template_last_label=..., template_lnb=1,
kw1=struct, ns1=std, pre1_t=conjunction,
last=;)]
이 메타 함수는 매개변수로 전달된 다수의 자료형에서 정적 데이터 멤버 valuebool로 읽어서 전부 true인지 아닌지 검사하는 기능을 한다. 즉 자료형에 대하여 &&의 역할을 수행한다. std::conjunction_v로 값을 읽는 유틸리티 템플릿도 제공된다.
#!syntax cpp
// (1) `result0`의 값은 true
// conjunction<...>::value도 사용 가능함.
constexpr bool result0 = std::conjunction_v<std::bool_constant<true>, std::bool_constant<true>>;

// (2) `result1`의 값은 false
constexpr bool result1 = std::conjunction_v<std::bool_constant<true>, std::bool_constant<false>>;

// (3) `result2`의 값은 false
constexpr bool result2 = std::conjunction_v<std::bool_constant<false>, std::bool_constant<false>>;
표준 라이브러리의 integral_constant<T, v>를 열거하는 데에 사용할 수 있다.
#!syntax cpp
import <type_traits>;

template<typename T>
struct CheckNothrowMarshallableTrait : std::bool_constant<std::is_trivially_copy_constructible_v<T>>
{};

template<typename... Ts>
struct MarshalTrait
{
    // conjunction_v 안에서 매개변수 묶음 확장
    static constexpr bool IsNothrowSerializable = std::conjunction_v<CheckNothrowMarshallableTrait<Ts>...>;
};
이 메타 함수를 앞서 구현한 직렬화 함수의 noexcept에 전달하면 완성된다.
#!syntax cpp
template<std::size_t BuffLen, typename... Ts>
auto WriteToBuffer(char(&buffer)[BuffLen], Ts&&... values) noexcept(IsNothrowMarshallable<Ts...>);
이 모든 과정은 컴파일 시점에 수행되므로 성능에는 아무 문제가 없다.

18.6.2. 정리

#!syntax cpp
import <type_traits>;
import <tuple>;
import <utility>;
import <memory>;

template<typename T>
struct CheckNothrowMarshallableTrait : std::bool_constant<std::is_trivially_copy_constructible_v<T>>
{};

template<typename... Ts>
struct MarshalTrait
{
  static constexpr bool IsNothrowSerializable = std::conjunction_v<CheckNothrowMarshallableTrait<Ts>...>;
};

template<typename... Ts>
constexpr bool IsNothrowMarshallable = MarshalTrait<Ts...>::IsNothrowSerializable;

template<typename... Ts>
constexpr std::size_t TotalByteSizeOf = (sizeof(Ts) + ...);

template<>
constexpr std::size_t TotalByteSizeOf<> = 0;

template<typename Tuple, std::size_t... Indices>
constexpr char* WriteToBufferImpl(char* ptr, Tuple&& tuple, std::index_sequence<Indices...>)
{
  constexpr auto writer = []<typename T>(char* ptr, T && value) -> std::size_t
  {
    memcpy(ptr, std::addressof(value), sizeof(T));

    return sizeof(T);
  };

  return ((ptr += writer(ptr, std::get<Indices>(std::forward<Tuple>(tuple)))), ...);
}

template<std::size_t BuffLen, typename... Ts>
constexpr char* WriteToBuffer(char(&buffer)[BuffLen], Ts&&... values) noexcept(IsNothrowMarshallable<Ts...>)
{
  static_assert(TotalByteSizeOf<Ts...> <= BuffLen);

  return WriteToBufferImpl(buffer, std::forward_as_tuple(std::forward<Ts>(values)...), std::index_sequence_for<Ts...>{});
}

int main()
{
  char buffer[32]{};

  // (1) 바이트 크기가 32인 버퍼에 적절한 수의 값을 쓰는 경우
  // 템플릿 함수 인스턴스는 void(int&&, short&&, int&&, long long&&, unsigned char&&) noexcept;
  // 전달된 자료형의 바이트 크기의 합은 sizeof(int) + sizeof(short) + sizeof(int) + sizeof(long long) + sizeof(unsigned char)로서
  // 4 + 2 + 4 + 8 + 1 = 15임.
  // 버퍼 오버플로우 없이, 예외 없이 작동함.
  WriteToBuffer(buffer, 400, short(100), -8000, 1000LL, u'A');

  // (2) 바이트 크기가 32인 버퍼가 넘치게 값을 쓰는 경우
  // 템플릿 함수 인스턴스는 void(long long&&, long long&&, long long&&, long long&&, bool&&) noexcept;
  // 전달된 자료형의 바이트 크기의 합은 sizeof(long long) + sizeof(long long) + sizeof(long long) + sizeof(long long) + sizeof(bool)로서
  // 8 + 8 + 8 + 8 + 1 = 33임.
  // 컴파일 오류 발생! 정적 어설션이 실패했습니다.
  // `static_assert(TotalByteSizeOf<Ts...> <= BuffLen);` 구문이
  // `static_assert(TotalByteSizeOf<long long, long long, long long, long long, long long, bool> <= 32);`로 컴파일이 실패한다.
  WriteToBuffer(buffer, 1LL, 2LL, 3LL, 4LL, true);

  // (3) 아무 인자도 전달되지 않은 경우
  // 아무 동작도 하지 않는다.
  WriteToBuffer(buffer);
}
작성된 코드는 위와 같다.

하지만 우린 아직 배고프다. 여전히 원시 자료형만 사용 가능하고 문자열에도 사용 불가능하다. 그럼 이제 사용자 정의 자료형 및 자명하지 않은 자료형에도 작동하게 만들어보자. 그러나 이를 위해서는 기반 지식이 더 필요하다. 메타 프로그래밍을 익혀야 다음 단계로 진행할 수 있다.

19. 메타 프로그래밍

파일:상세 내용 아이콘.svg   자세한 내용은 C++/문법/메타 프로그래밍 문서
#!if (문단 == null) == (앵커 == null)
를
#!if 문단 != null & 앵커 == null
의 [[C++/문법/메타 프로그래밍#s-|]]번 문단을
#!if 문단 == null & 앵커 != null
의 [[C++/문법/메타 프로그래밍#|]] 부분을
참고하십시오.

[include(틀:
[1] 일반화 프로그래밍[2] 메타 프로그래밍[3]static이 아닌[4] 예를 들어서 +-*/의 사칙연산식 또는 &&, || 의 논리식[5] 그냥 noexcept와 같음[6] noexcept를 안 단 것과 같음