콘텐츠로 이동

C++17 Fold Expressions

Fold Expression(폴드 표현식)은 C++17에서 도입된 가변 인자 템플릿(variadic template) 확장 문법입니다. 파라미터 팩(parameter pack)의 원소에 이항 연산자를 순차적으로 적용해 하나의 값으로 축약합니다. 재귀 템플릿 없이 한 줄로 가변 인자 합산, 출력, 조건 검사를 표현할 수 있습니다.


// 단항 우폴드 (Unary Right Fold): (pack op ...)
// 단항 좌폴드 (Unary Left Fold): (... op pack)
// 이항 우폴드 (Binary Right Fold): (pack op ... op init)
// 이항 좌폴드 (Binary Left Fold): (init op ... op pack)
#include <iostream>
// 단항 우폴드: e1 + (e2 + (e3 + e4))
template<typename... Args>
auto Sum(Args... args)
{
return (args + ...); // 우폴드
}
// 단항 좌폴드: ((e1 + e2) + e3) + e4
template<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 (덧셈은 결합법칙 성립)
}

파라미터 팩이 비어있을 때 기본값을 제공해야 하는 경우 이항 폴드를 사용합니다.

// 빈 팩 처리: 단항 폴드는 빈 팩에서 컴파일 오류
// (단, &&, ||, 쉼표 연산자는 예외적으로 빈 팩 허용)
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); // 24
All(); // true
Any(); // false

template<typename... Args>
void Print(Args&&... args)
{
// 쉼표 폴드로 순서 보장 출력
((std::cout << args << ' '), ...);
std::cout << '\n';
}
Print(1, "hello", 3.14f, true);
// 출력: 1 hello 3.14 1
template<typename T, typename... Types>
constexpr bool IsAnyOf = (std::is_same_v<T, Types> || ...);
static_assert(IsAnyOf<int, float, int, double>); // true
static_assert(!IsAnyOf<char, float, int, double>); // true
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}
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
);

#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.14

// 재귀 템플릿 방식 (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), ...)는 반환값 없이 사이드 이펙트를 순서대로 실행할 때 유용하다.