C++17 Fold Expressions
Fold Expression(폴드 표현식)은 C++17에서 도입된 가변 인자 템플릿(variadic template) 확장 문법입니다. 파라미터 팩(parameter pack)의 원소에 이항 연산자를 순차적으로 적용해 하나의 값으로 축약합니다. 재귀 템플릿 없이 한 줄로 가변 인자 합산, 출력, 조건 검사를 표현할 수 있습니다.
1. 네 가지 폴드 형식
섹션 제목: “1. 네 가지 폴드 형식”// 단항 우폴드 (Unary Right Fold): (pack op ...)// 단항 좌폴드 (Unary Left Fold): (... op pack)// 이항 우폴드 (Binary Right Fold): (pack op ... op init)// 이항 좌폴드 (Binary Left Fold): (init op ... op pack)1.1 단항 폴드 예시
섹션 제목: “1.1 단항 폴드 예시”#include <iostream>
// 단항 우폴드: e1 + (e2 + (e3 + e4))template<typename... Args>auto Sum(Args... args){ return (args + ...); // 우폴드}
// 단항 좌폴드: ((e1 + e2) + e3) + e4template<typename... Args>auto SumLeft(Args... args){ return (... + args); // 좌폴드}
int main(){ std::cout << Sum(1, 2, 3, 4); // 10 std::cout << SumLeft(1, 2, 3, 4); // 10 (덧셈은 결합법칙 성립)}2. 이항 폴드 — 초기값 지정
섹션 제목: “2. 이항 폴드 — 초기값 지정”파라미터 팩이 비어있을 때 기본값을 제공해야 하는 경우 이항 폴드를 사용합니다.
// 빈 팩 처리: 단항 폴드는 빈 팩에서 컴파일 오류// (단, &&, ||, 쉼표 연산자는 예외적으로 빈 팩 허용)
template<typename... Args>auto Product(Args... args){ return (args * ... * 1); // 이항 우폴드, 초기값 1}
template<typename... Args>auto All(Args... args) -> bool{ return (args && ... && true); // 모두 참인지 확인}
template<typename... Args>auto Any(Args... args) -> bool{ return (args || ... || false);}
// 사용Product(); // 1 (초기값)Product(2, 3, 4); // 24All(); // trueAny(); // false3. 실용 패턴
섹션 제목: “3. 실용 패턴”3.1 가변 출력
섹션 제목: “3.1 가변 출력”template<typename... Args>void Print(Args&&... args){ // 쉼표 폴드로 순서 보장 출력 ((std::cout << args << ' '), ...); std::cout << '\n';}
Print(1, "hello", 3.14f, true);// 출력: 1 hello 3.14 13.2 타입 체크
섹션 제목: “3.2 타입 체크”template<typename T, typename... Types>constexpr bool IsAnyOf = (std::is_same_v<T, Types> || ...);
static_assert(IsAnyOf<int, float, int, double>); // truestatic_assert(!IsAnyOf<char, float, int, double>); // true3.3 컨테이너에 여러 원소 삽입
섹션 제목: “3.3 컨테이너에 여러 원소 삽입”template<typename Container, typename... Values>void PushAll(Container& c, Values&&... vals){ (c.push_back(std::forward<Values>(vals)), ...);}
std::vector<int> v;PushAll(v, 1, 2, 3, 4, 5);// v == {1, 2, 3, 4, 5}3.4 함수 체인 호출
섹션 제목: “3.4 함수 체인 호출”template<typename T, typename... Funcs>T ApplyAll(T value, Funcs&&... funcs){ return (std::forward<Funcs>(funcs)(value), ...), // 쉼표 폴드 (반환값 무시) value; // 마지막 변환 결과 반환이 필요하면 다른 방식 사용}
// 반환값을 체이닝하려면:template<typename T, typename... Funcs>T Pipeline(T init, Funcs&&... funcs){ ((init = std::forward<Funcs>(funcs)(init)), ...); return init;}
auto result = Pipeline(5, [](int x) { return x * 2; }, // 10 [](int x) { return x + 3; }, // 13 [](int x) { return x * x; } // 169);4. 인덱스 시퀀스와 결합
섹션 제목: “4. 인덱스 시퀀스와 결합”#include <utility>#include <tuple>
// 튜플의 모든 원소 출력template<typename Tuple, size_t... Is>void PrintTupleImpl(const Tuple& t, std::index_sequence<Is...>){ ((std::cout << std::get<Is>(t) << ' '), ...);}
template<typename... Ts>void PrintTuple(const std::tuple<Ts...>& t){ PrintTupleImpl(t, std::index_sequence_for<Ts...>{});}
PrintTuple(std::make_tuple(1, "hello", 3.14));// 출력: 1 hello 3.145. 컴파일 타임 합산 성능
섹션 제목: “5. 컴파일 타임 합산 성능”// 재귀 템플릿 방식 (C++11)template<int First, int... Rest>struct SumOld { static constexpr int value = First + SumOld<Rest...>::value; };template<int Last>struct SumOld<Last> { static constexpr int value = Last; };
// 폴드 표현식 방식 (C++17) — 코드가 단순하고 컴파일 속도도 빠름template<int... Ns>constexpr int SumNew = (Ns + ... + 0);
static_assert(SumNew<1, 2, 3, 4, 5> == 15);재귀 템플릿은 깊이만큼 인스턴스화가 발생하지만, 폴드 표현식은 단일 인스턴스화로 처리됩니다.
- 폴드 표현식은 파라미터 팩을 반복 처리하는 재귀 템플릿을 대체해 코드를 단순화한다.
- 단항 폴드는 팩이 비어있을 때
&&,||,,연산자만 허용한다. 다른 연산자는 이항 폴드로 초기값을 지정한다. - 좌폴드
(... op pack)과 우폴드(pack op ...)는 연산 순서가 다르므로 비교환 연산자(빼기, 나누기)에서 결과가 달라진다. - 쉼표 폴드
((expr), ...)는 반환값 없이 사이드 이펙트를 순서대로 실행할 때 유용하다.