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);재귀 템플릿은 깊이만큼 인스턴스화가 발생하지만, 폴드 표현식은 단일 인스턴스화로 처리됩니다.
6. 실전 패턴 심화
섹션 제목: “6. 실전 패턴 심화”6.1 조건부 실행
섹션 제목: “6.1 조건부 실행”// 모든 조건이 참일 때만 실행template<typename F, typename... Predicates>void execute_if_all(F&& func, Predicates&&... preds){ if ((std::forward<Predicates>(preds)() && ...)) std::forward<F>(func)();}
bool is_logged_in() { return true; }bool has_permission() { return true; }bool is_connected() { return false; }
execute_if_all( []{ std::cout << "실행됨\n"; }, is_logged_in, has_permission, is_connected); // is_connected가 false이므로 실행 안 됨6.2 오버로드 집합 (Overload Set)
섹션 제목: “6.2 오버로드 집합 (Overload Set)”// 여러 람다를 하나의 오버로드 집합으로 합치기template<typename... Lambdas>struct Overloaded : Lambdas...{ using Lambdas::operator()...; // 폴드 표현식으로 모든 operator() 가져오기};
// C++17 CTAD 가이드 (C++20부터는 불필요)template<typename... Ts>Overloaded(Ts...) -> Overloaded<Ts...>;
// 사용 — std::variant 방문에 매우 유용#include <variant>using Shape = std::variant<int, double, std::string>;
Shape s = 3.14;std::visit(Overloaded{ [](int v) { std::cout << "int: " << v; }, [](double v) { std::cout << "double: " << v; }, [](const std::string& v) { std::cout << "string: " << v; }}, s);// 출력: double: 3.146.3 타입 목록에 변환 적용
섹션 제목: “6.3 타입 목록에 변환 적용”#include <tuple>
// 파라미터 팩의 각 타입을 변환해 새 tuple 타입 생성template<template<typename> class Transform, typename... Ts>using transform_types = std::tuple<Transform<Ts>...>;
// 모든 타입을 vector로 변환template<typename T> using to_vector = std::vector<T>;using VectorTuple = transform_types<to_vector, int, double, std::string>;// std::tuple<vector<int>, vector<double>, vector<string>>
// 모든 타입을 const 참조로 변환template<typename T> using to_const_ref = const T&;using RefTuple = transform_types<to_const_ref, int, double>;// std::tuple<const int&, const double&>7. 빈 팩 처리 규칙 요약
섹션 제목: “7. 빈 팩 처리 규칙 요약”| 연산자 | 빈 팩 단항 폴드 결과 | 이항 폴드 사용 가능 |
|---|---|---|
&& | true | (true && ... && pack) |
|| | false | (false || ... || pack) |
, | void() | (void(), ..., pack) |
+, -, *, / | 컴파일 오류 | 이항 폴드 필수 |
<< | 컴파일 오류 | 이항 폴드 필수 |
// 빈 팩 안전 패턴template<typename... Args>auto safe_sum(Args... args) { return (0 + ... + args); } // 빈 팩 → 0template<typename... Args>auto safe_product(Args... args) { return (1 * ... * args); } // 빈 팩 → 1template<typename... Args>bool safe_all(Args... args) { return (true && ... && args); } // 빈 팩 → truetemplate<typename... Args>bool safe_any(Args... args) { return (false || ... || args); } // 빈 팩 → false- 폴드 표현식은 파라미터 팩을 반복 처리하는 재귀 템플릿을 대체해 코드를 단순화한다.
- 단항 폴드는 팩이 비어있을 때
&&,||,,연산자만 허용한다. 다른 연산자는 이항 폴드로 초기값을 지정한다. - 좌폴드
(... op pack)과 우폴드(pack op ...)는 연산 순서가 다르므로 비교환 연산자(빼기, 나누기)에서 결과가 달라진다. - 쉼표 폴드
((expr), ...)는 반환값 없이 사이드 이펙트를 순서대로 실행할 때 유용하다. Overloaded패턴(using Lambdas::operator()...)은std::variant방문에 매우 실용적이다.