c++11부터 parameter pack라는 기능이 제공되었습니다. 

parameter pack을 사용하면 0개 이상의 템플릿 인자를 사용할 수 있습니다. 

아래 예제처럼 템플릿 파라미터를 "typename... Targs" 형식으로 선언하여 가변 인자를 처리 하도록 구성 할 수 있습니다. 

#include <iostream>

using namespace std;

template<typename T>
constexpr auto tsum(T value) // 기본 함수
{
    return value;
}

template<typename T, typename... Targs>
constexpr auto tsum(T value, Targs... args) // 재귀 가변 함수
{
    return value + tsum(args...);
}


int main()
{
    constexpr auto sum = tsum(1, 2, 3, 4, 5, 6);
    cout << sum << '\n'; // 21
    return 0;
}

c++ 17부터는 fold expression이 제공되어서 parameter pack을 더욱 손쉽게 사용 할 수 있게 되었습니다. 

fold expression 의 테스트 환경

3중 비교 연산자 컴파일러 지원 옵션

Visual Studio 사용시 프로젝트 우 클릭 -> 구성 속성 -> 일반 -> c++ 언어 표준을 ISO C++ 17 이상으로 수정

VisualStudio 버전별 컴파일러 버전

fold expression 사용 예제

fold expression을 사용하면 위에서 설명드린 예제를 쉽게 처리 할 수 있습니다.

#include <iostream>

using namespace std;

template<typename... Targs>
constexpr auto tsum(Targs... args) // 재귀 가변 함수
{
    return (args + ...); // 단항 오른쪽 fold
}

int main()
{
    constexpr auto sum = tsum(1, 2, 3, 4, 5, 6);
    cout << sum << '\n'; // 21

    return 0;
}

fold expression 문법

fold expression은 4가지 문법으로 사용할 수 있습니다. 

단항 오른쪽 fold

다음과 같은 문법으로 사용 할 수 있습니다. 

pack op ... 
template<typename... Args>
auto sum(Args... args) { 
    return (args + ...); 
}

int main(){
    sum(1, 2, 3, 4);
}

단항 오른쪽 fold는 전달받은 parameter pack을 다음과 같은 표현식으로 변경 합니다.

E1 op ( ... op ( En-1 op En))

뒤에 있는 인자 부터 먼저 순차적으로 연산해서 결과 값을 생성합니다. 

아래의 코드는 위의 예제를 컴파일러가 어떻게 생성하는지 보여줍니다. 

template<typename... Args>
auto sum(Args... args) { 
    return (args + ...); 
}

#ifdef INSIGHTS_USE_TEMPLATE
template<>
int sum<int, int, int, int>(int __args0, int __args1, int __args2, int __args3)
{
    return __args0 + (__args1 + (__args2 + __args3));
}
#endif

int main()
{
    sum(1, 2, 3, 4);
    return 0;
}

단항 왼쪽 fold

...  op pack
template<typename... Args>
auto sum(Args... args) { 
    return (... + args); 
}

int main(){
    sum(1, 2, 3, 4);
}

전달받은 parameter pack을 다음과 같은 표현식으로 변경 합니다.

((E1 op E2) op ...) op En

앞에 있는 인자 부터 순차적으로 연산해서 결과 값을 생성합니다. 

컴파일러가 생성한 단항 왼쪽 fold는 다음과 같습니다.

// 생략...
template<>
int sum<int, int, int, int>(int __args0, int __args1, int __args2, int __args3)
{
    return ((__args0 + __args1) + __args2) + __args3;
}
// 생략...

 

이항 오른쪽 fold

단항을 사용하는 문법과 다르게 parameter pack과 별개로 초기 연산할 값을 추가 할 수 있습니다. 

pack op ... op init
template<typename ...Args>
int sum(Args&&... args)
{
    return (args + ... + (1 * 2));
}

전달받은 parameter pack을 다음과 같은 표현식으로 변경 합니다.

E1 op ( ... op ( En-1 op ( En op I )))

이항 왼쪽 fold

init op ... op pack
template<typename ...Args>
int sum(Args&&... args)
{
    return ((1 * 2) + ... + args);
}

전달받은 parameter pack을 다음과 같은 표현식으로 변경 합니다.

((( I op E1 ) op E2) op ...) op En

단항 fold에 길이가 0인 parameter pack이 전달이 가능한 경우

  • AND(&&) 연산자 인경우 true를 리턴 합니다. 
  • OR(||) 연산자인 경우 false를 리턴 합니다.
  • Comma(,) 연산자 인경우 void를 리턴합니다. 
#include <iostream>
using namespace std;

template<typename... Args>
bool all(Args... args) 
{ 
    return (... && args); 
}

template<typename... Args>
bool any(Args... args)
{
    return (... || args);
}

template<typename... Args>
void comma(Args... args)
{
    return (... , args);
}


int main() {
    auto a = all();
    auto b = any();
    //auto c = comma(); // error void 리턴
    comma();

    cout << boolalpha;
    cout << "a : " << a << ", b : " << b << '\n';    // a : true, b : false
}

fold expression으로 만드는 간단한 헬퍼 함수

아래의 예제는 첫번째 인자 컨테이너에 매칭 되는 가변 인자의 수를 리턴하는 함수입니다. 

// 전달 받은 가변인수 ts만큼 range를 조회해서 카운트를 구합니다.
template<typename R, typename ... Ts>
auto matches(const R& range, Ts ... ts)
{
    return (std::count(std::begin(range), std::end(range), ts) + ...);
}

void main()
{
    vector<int> v{ 1,2,3,4,5 };
    cout << matches(v, 2, 5) << '\\n';           // 2 반환
    cout << matches(v, 100, 200) << '\\n';       // 0 반환
    cout << matches("abcdefg", 'x', 'y', 'z') << '\\n';       // 0 반환
    cout << matches("abcdefg", 'a', 'd', 'f') << '\\n';       // 3 반환

}

다음 예제는 Set에 가변인자를 insert하고 모두 성공했는지 확인 하는 헬퍼함수입니다. 

// 전달 받은 가변인수 모두를 set에 삽입하고 모두 성공했는지 반환 받습니다.
template<typename T, typename ... Ts>
bool insert_all(T & set, Ts ... ts){
    // set.insert의 반환 값을 pair<iterator, bool>의 값을 가짐
    return (set.insert(ts).second && ...);
}

void main()
{
    set<int> my_set{ 1, 2, 3 };
    cout << insert_all(my_set, 4, 5, 6) << '\\n';        // true
    cout << insert_all(my_set, 7, 8, 2) << '\\n';        // false => 2 값을 insert 중복으로 false
}

'c++ > modern c++' 카테고리의 다른 글

[c++20] 3중 비교 연산자 (<=>)  (0) 2022.06.18
[c++20] consteval과 constinit  (0) 2022.06.16
[c++20] module(모듈)  (0) 2022.06.13
[c++20] Concept  (0) 2022.06.10
[c++] condition variable(조건 변수)  (0) 2020.07.10

+ Recent posts