38. 가변 인자 템플릿·파라미터 팩 전개·fold expression
난이도 중내 리뷰 · C++
해설 · C++
해설 — 가변 인자 템플릿·파라미터 팩 전개·fold expression
난이도: 중상
요약
가변 인자 템플릿은 파라미터 팩을 컴파일 타임에 펼쳐 임의 개수 인자를 타입 안전하게 처리한다. 전개 방식은 두 가지다: (1) 재귀(하나 벗겨 나머지로 재귀), (2) C++17 fold expression((pack op ...))으로 한 표현식에 접기. 출력은 다음과 같다.
10
10
abc
10,20,30
x,y
4
핵심 개념
- 파라미터 팩
typename... Args/Args... args: 0개 이상의 타입/값 묶음.sizeof...(Args)로 개수를 컴파일 타임에 얻는다. - 팩 전개(expansion)
pattern...: 팩을 콤마로 구분해 펼친다.sum_recursive(rest...)는rest의 각 원소를 인자로 나열. - fold expression(C++17):
- 단항 우측 폴드
(E op ...)→E1 op (E2 op (... op EN)) - 단항 좌측 폴드
(... op E)→((E1 op E2) op ...) op EN - 이항 폴드
(E op ... op init)는 빈 팩에서init을 결과로.
- 단항 우측 폴드
단계별 내부 동작
sum_recursive(1,2,3,4)→ 10: 컴파일러가sum_recursive<int,int,int,int>→1 + sum_recursive<int,int,int>(2,3,4)→ … → 기저sum_recursive<int>(4)=4. 즉 4개의 서로 다른 오버로드가 인스턴스화되어1+(2+(3+4))로 접힌다. 각 재귀 단계가 별도 함수 인스턴스라 코드 크기가 인자 수에 비례.sum_fold(1,2,3,4)→ 10:(args + ...)는 단항 우측 폴드1 + (2 + (3 + 4)). 재귀 없이 하나의 함수 본문에서 표현식 트리로 전개된다.sum_fold("a","b","c")→ "abc": 우측 폴드라a + (b + c).std::string operator+로 접합. 좌측 폴드였다면(a+b)+c로 결합 순서가 달라진다(문자열은 결과 동일하지만 부동소수점 합처럼 결합 순서가 값에 영향 주는 타입도 있다).print_all(10,20,30)→10,20,30:((std::cout << sep << args), ...)는 콤마 연산자 폴드. 콤마 연산자는 좌→우 평가 순서와 시퀀스 포인트를 보장하므로 인자가 선언 순서대로 출력된다.i++로 첫 항만 구분자 생략.count_args(1,2.0,'c',"s")→ 4:sizeof...(Args)는 순수 컴파일 타임 상수 →constexpr std::size_t n초기화 가능. 인자 값은 무시하고 팩 크기만 센다.
흔한 오해·함정
- 폴드 방향 혼동:
(args + ...)는 우측 폴드다. 좌측을 원하면(... + args). 뺄셈·나눗셈처럼 비결합 연산에서 결과가 달라진다. - 빈 팩:
(args + ...)에 인자가 0개면 컴파일 오류(단항 폴드는&&,||,,만 빈 팩 허용). 안전하려면 이항 폴드(args + ... + 0)로 초기값을 준다. - 콤마 폴드의 평가 순서:
(f(args), ...)는 순서가 보장되지만,printf("%d%d", args...)같은 함수 호출 인자 전개는 인자 평가 순서가 미지정이다 — 부수효과 순서에 의존하면 안 된다. - 재귀 전개의 코드 팽창: 인자 수만큼 오버로드가 생겨 컴파일 시간·바이너리 증가. fold 나
if constexpr로 단일 본문화하면 개선.
면접 포인트
- 재귀 전개 vs fold expression 의 차이(인스턴스화 개수·가독성·빈 팩 처리)와, C++17 이전에는 왜 재귀/
initializer_list트릭을 썼는지. - 좌측/우측 폴드의 결합 순서가 실제 결과에 영향을 주는 사례(부동소수점 누적, 비가환 연산).
- 콤마 폴드로 "각 인자에 순서대로 부수효과"를 안전하게 적용하는 관용구와, 함수 인자 전개의 평가 순서 미지정 함정.