C++17
부터 도입된 폴드 표현식(fold expression)
은 개수가 정해지지 않은 매개변수를 하나로 묶은 매개변수 팩(parameter pack)
을 반복해서 계산해 준다. C++11
부터 도입된 가변 인수 템플릿(variadic templates)
의 재귀 호출을 함수 하나로 표현할 수 있는 방법이다. 폴더 표현식
은 어려운 문법에 속하지만 소스 코드를 간결하게 만들 수 있는 좋은 방법이다.
함수의 매개변수 개수는 일반적으로 소스 코드를 작성하는 시점에 정해진다. 하지만 매개변수의 개수를 알 수 없거나 필요에 따라 가변 개수의 인자를 허용할 때가 있다. 예를 들어 C
언어에서 printf
함수를 호출할 때를 떠올려 보녀 첫 번째 인자의 형식에 따라 나머지 인자의 개수가 결정된다.
printf("%d %d %c", 1, 10, 'a');
모던 C++
에서는 가변 인자를 허용하는 함수를 선언할 때 템플릿과 매개변수 팩을 사용한다. 매개변수 팩
은 데이터 형식과 개수가 모두 정해지지 않은 템플릿 매개변수들을 하나로 묶어 준다.
다음 코드는 매개변수 팩을 이용한 가변 인자 템플릿과 재귀 호출을 구현한 예이다.
#include <iostream>
#include <array>
#include <algorithm>
using std::cout;
using std::endl;
// 매개변수 팩을 이용한 가변 인자 템플릿 - 말단 함수용
template<typename doll>
// 매개변수 팩의 마지막 인자를 전달받는 말단 함수
void find_doll(doll doll_name) {
cout << "'" << doll_name << "'(이)가 ";
}
// 매개변수 팩을 이용한 가변 인자 템플릿 - 재귀 함수용
template<typename doll, typename... dolls>
// find_dell 함수 오버로딩
void find_doll(doll doll_name, dolls... doll_list) {
cout << "'" << doll_name << "' 안쪽에 ";
// 재귀 호출
find_doll(doll_list...);
}
int main()
{
cout << "first" << endl;
// 가변 인자로 함수 호출
find_doll("big", "middle", "small");
cout << "있습니다." << endl;
cout << "second" << endl;
// 가변 인자로 함수 호출
find_doll("bigbig", "big", "middle", "small", "tiny");
cout << "있습니다." << endl;
return 0;
}
실행 결과
first
'big' 안쪽에 'middle' 안쪽에 'small'(이)가 있습니다.
second
'bigbig' 안쪽에 'big' 안쪽에 'middle' 안쪽에 'small' 안쪽에 'tiny'(이)가 있습니다.
매개변수 팩을 처리하려면 재귀 함수와 마지막 매개변수를 처리할 말단 함수를 정의해야 한다. 이때 두 함수의 이름은 같아야 한다.
즉, 하나는 오버로딩
해야 한다. 이를 전제로 코드를 보면 오버로딩한 find_doll
함수의 마지막 줄에는 find_doll(doll_list...)
처럼 재귀 호출
한다. 이렇게 재귀 호출
하면 doll_list
의 첫 번째 원소가 doll_name
으로 전달되고, 첫 번째 원소를 제외한 원소들은 doll_list
인 매개변수 팩으로 전달된다.
매개변수 팩은 매개변수 구성을 간단하게 할 수 있어 활용도가 높은 문법이다. 하지만 재귀 함수
설계에 익숙하지 않은 개발자는 코드를 작성하기가 어려울 수 있다. 이러한 문제점을 보완하여 폴드 표현식
은 매개변수 팩을 수비게 사용할 수 있게 해준다.
폴드 표현식
은 보통 괄호와 연산자, 점 3개로 표현한다. 폴드 표현식
은 프로그래밍이 실행되면 매개변수 팩의 원소를 펼쳐서 지정된 연산을 차례로 수행한다.
(매개변수_팩_이름 연산자 ...)
다음 코드는 폴드 표현식으로 수정한 것이다. find_doll
함수에서 show_doll
함수를 호출할 때 매개변수 팩의 원소를 모두 펼쳐 개별 원소를 대상으로 같은 연산을 반복한다.
폴드 표현식
에 덧셈 연산자(+)
를 사용했으므로 매개변수 팩의 모든 원소를 순회하며 문자열을 합해서 출력한다.
#include <iostream>
using std::cout;
using std::endl;
template<typename doll>
doll show_doll(doll doll_name) {
return "'" + doll_name + "' 안쪽에 ";
}
template<typename... dolls>
std::string find_doll(dolls... doll_list) {
// 폴드 표현식 사용
return (show_doll(doll_list) + ...);
}
int main()
{
cout << "first" << endl;
cout << find_doll(std::string("big"), std::string("middle"), std::string("small"));
cout << "있습니다." << endl << endl;
cout << "second" << endl;
cout << find_doll(std::string("bigbig"), std::string("big"), std::string("middle"), std::string("small"), std::string("tiny"));
cout << "있습니다." << endl << endl;
return 0;
}
실행 결과
first
'big' 안쪽에 'middle' 안쪽에 'small' 안쪽에 있습니다.
second
'bigbig' 안쪽에 'big' 안쪽에 'middle' 안쪽에 'small' 안쪽에 'tiny' 안쪽에 있습니다.
폴드 표현식
은 단항이나 이항의 왼쪽, 오른쪽 폴드 표현식으로 나뉜다. 매개변수 팩 한 가지로만 폴드 표현식을 사용하면 단항, 매개변수 팩과 초기값 두 가지로 폴드 표현식을 사용하면 이항
이라고 한다.
다음 표에서는 네 가지 폴드 표현식의 형태를 보여준다. 정수 4개로 구성된 매개변수 팩(int_pack)을 덧셈(+)하는 폴드 표현식의 연산 순서를 유형별로 알 수 있다.
폴드 표현식 | 유형 | 실행 연산(초기값 init은 0으로 설정) |
---|---|---|
(int_pack + ...) | 단항 오른쪽 폴드 표현식 | (((1 + 2) + 3) + 4) |
(... + int_pack) | 단항 왼쪽 폴드 표현식 | (1 + (2 + (3 + 4))) |
(int_pack + ... + init) | 이항 오른쪽 폴드 표현식 | ((((0 + 1) + 2) + 3) + 4) |
(init + ... + int_pack) | 이항 왼쪽 폴드 표현식 | (0 + (1 + (2 + (3 + 4)))) |
덧셈 연산은 연산 순서와 결과가 다르지 않지만, 나누기 연산 등 다른 연산은 결과가 달라질 수 있으므로 주의해야 한다. 폴드 표현식
에서 사용할 수 있는 연산자는 다음과 같다.
+, -, *, /, %, ^, &, |, =, <, >, <<, >>
+=, -=, *=, /=, %=, ^=, &=, |=, <<=, >>=, ==, !=, <=, >=, &&, ||, ,(쉼표), .(마침표), *, ->*
실수형 데이터를 나누는 폴드 표현식의 예를 보면 쉽게 이해할 수 있다. 다음 코드에서는 같은 가변 인자를 사용했더라도 왼쪽 폴드 표현식과 오른쪽 폴드 표현식의 계산 결과가 다른 것을 알 수 있다. 이항에서는 10.0을 초기값으로 설정하여 단항과 10배의 차이가 나는 것을 확인할 수 있다.
// 단항 왼쪽 폴드 표현식
template<typename... numbers>
double unary_left(numbers... num_list) {
return (... / num_list);
}
// 단항 오른쪽 폴드 표현식
template<typename... numbers>
double unary_right(numbers... num_list) {
return (num_list / ...);
}
// 이항 왼쪽 폴드 표현식
template<typename... numbers>
double binary_left(numbers... num_list) {
return (10.0 / ... / num_list);
}
// 이항 오른쪽 폴드 표현식
template<typename... numbers>
double binary_right(numbers... num_list) {
return (num_list / ... / 10.0);
}