4 분 소요

개요

가변 인자(…)에서 ...을 이용한 가변 인자를 소개해 드렸는데요(가변 인자(…) 참고),

C++11 부터는 가변 템플릿파라메터 팩이 추가되어, 가변 인자(…)와 같이 갯수와 타입이 정해 지지 않은 템플릿 인자를 사용할 수 있습니다.

가변 템플릿을 이용하면 다음처럼 재귀적으로 호출하여 합계를 구현할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename Type>
Type Sum_11(Type param) {
    return param; // 재귀 호출하다가 마지막에 도달하면 호출됩니다.
}

// Types는 파라메터 팩입니다.
template<typename Type, typename... Types>
Type Sum_11(Type param, Types... params) {
    // 재귀 호출시 params의 첫번째 인자인 params[1]은 param으로 전달되고, 
    // 나머지 params[2] ~ params[N] 은 params에 전달됩니다.
    return param + Sum_11(params...); 
} 

int val{Sum_11(1, 3, 5)};
EXPECT_TRUE(val == 1 + 3 + 5);  

파라메터 팩 배포 및 확장

함수 인자로 전달된 파라메터 팩params...와 같이 파라메터명 뒤에 ...을 붙여 사용할 수 있습니다.

항목 내용
template<typename... Types> 여러 타입들로 구성된 파라메터 팩입니다.
template<int... Values> 비타입 템플릿 인자(예를 들어 int)로 구성된파라메터 팩입니다.

다음과 같이 표현식을 이용한 패턴으로 확장하여 배포할 수 있습니다.(패턴에는 파라메터 팩 이름이 포함되어야 합니다.)

1
2
3
4
5
6
7
8
9
f(params...); // f(params[1], params[2], params[3]) 로 전개됩니다.
f(++params...); // f(++params[1], ++params[2], ++params[3]) 로 전개됩니다.
f((params + 1)...); // f((params[1] + 1), (params[2] + 1), (params[3] + 3)) 로 전개됩니다.
f(&params...); // f(&params[1], &params[2], &params[3]) 로 전개됩니다.
f(vector[params]...); // f((vector[params[1]], vector[params[2]] vector[params[3]]) 로 전개됩니다.
f(g(params)...); // f(g(params[1]), g(&params[2]), g(&params[3])) 로 전개됩니다.
f(g(params...) + params...); // f(g(params[1], params[2], params[3]) + params[1], 
                             //   g(params[1], params[2], params[3]) + params[2], 
                             //   g(params[1], params[2], params[3]) + params[3]) 로 전개됩니다.

다음은 파라메터 팩을 확장하여 배포한 예입니다. Func_11() 함수는 함수 인자로 전달받은 params를 전개할 때 (params + 1)을 하여 1씩 더해서 배포합니다.

1
2
3
4
5
6
7
8
9
int Sum(int a, int b, int c) {
    return a + b + c;
}
template<typename... Params>
int Func_11(Params... params) {
    return Sum((params + 1)...); // 파라메터 팩의 각 요소에 1을 더해 배포합니다.
}

EXPECT_TRUE(Func_11(1, 2, 3) == 2 + 3 + 4);

(C++17~) Fold 표현식이 추가되어 가변 템플릿에서 파라메터 팩을 재귀적으로 반복하여 전개할 수 있습니다.

가변 템플릿을 이용한 포워딩 함수

또한, 다음처럼 전달 참조forward()를 이용하면 함수 인자의 갯수나 타입이 가변적인 포워딩 함수도 손쉽게 만들 수 있습니다.(forward() 원리 참고)

1
2
3
4
5
6
7
8
9
10
11
// func(params...)를 호출합니다.
template<typename Func, typename... Params>
int Forwarding_11(Func func, Params&&... params) {
    return func(std::forward<Params>(params)...);
}

int MySum(int a, int b, int c) {
    return a + b + c;
}

EXPECT_TRUE(Forwarding_11(MySum, 1, 2, 3) == 1 + 2 + 3);   

(C++17~) invoke()가 추가되어 일반 함수와 멤버 함수를 동일한 방식으로 호출할 수 있습니다. 상기 Forwarding_11()과 유사합니다.

sizeof…() 연산자

C++11 부터는 sizeof…() 연산자가 추가되었습니다.

sizeof() 연산자는 개체의 크기를 리턴하는데요(sizeof() 연산자 참고), sizeof…() 연산자파라메터 팩의 인자수를 리턴합니다.

1
2
3
4
5
6
template<typename... Types>
int Func_11(Types... params) {
    return sizeof...(params);
}

EXPECT_TRUE(Func_11(1, 2, 3) == 3);

(C++17~) Fold 표현식

기존에는 파라메터 팩에서 패턴을 활용하여 확장하여 전개할 수 있었는데요(파라메터 팩 배포 및 확장 참고), 패턴이 단순히 반복되는 형태였죠.

1
2
3
4
5
6
7
8
9
int Sum(int a, int b, int c) {
    return a + b + c;
}
template<typename... Params>
int Func_11(Params... params) {
    return Sum((params + 1)...); // Sum(params[1] + 1, params[2] + 1, params[3] + 1) 으로 전개됩니다.
} 

EXPECT_TRUE(Func(1, 2, 3) == 2 + 3 + 4);

C++17 부터는 Fold 표현식을 이용하여 파라메터 팩을 재귀적으로 반복하여 전개할 수 있습니다.

항목 내용
(params op...) 단항 오른쪽 Fold 표현식
(params[1] op (... op (params[N-1] op params[N]))) 로 전개
(params op...op init) 이항 오른쪽 Fold 표현식
(params[1] op (... op (params[N] op init))) 로 전개
(...op params) 단항 왼쪽 Fold 표현식
(((params[1] op params[2]) op ...) op params[N]) 로 전개
(init op...op params) 이항 왼쪽 Fold 표현식
(((init op params[1]) op ...) op params[N]) 로 전개

op는 다음 연산자들을 사용할 수 있습니다.

+ - * / % ^ & | = < > << >> += -= *= /= %= ^= &= |= <<= >>= == != <= >= && || , .* ->*

(params +...)에서 괄호 자체도 Fold 표현식의 일부 입니다. 따라서 return params +...;와 같이 괄호를 생략하면 컴파일 오류가 발생합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
template<typename... Params>
int UnaryRightFold_17(Params... params) {
    // (params[1] + (나머지 전개)) 
    // (params[1] + (params[2] + (나머지 전개)))
    // (params[1] + (params[2] + (params[3] + (나머지 전개))))
    // 즉, (params[1] op (... op (params[N-1] op params[N]))) 으로 전개 됩니다.        
    return (params +...); 
}  
// UnaryRightFold_17 과 동일하게 전개되나 마지막에 init 을 추가합니다.
template<typename... Params>
int BinaryRightFold_17(int init, Params... params) {
    // (params[1] + (나머지 전개 + val)) 
    // (params[1] + (params[2] + (나머지 전개 + val)))
    // (params[1] + (params[2] + (params[3] + (나머지 전개))))
    // 즉, (params[1] op (... op (params[N] op init))) 으로 전개 됩니다.        
    return (params +...+ init); 
} 
template<typename... Params>
int UnaryLeftFold_17(Params... params) {
    // ((나머지 전개) + params[N]) 
    // (((나머지 전개) + params[N-1]) + params[N]) 
    // ((((나머지 전개) + params[N-2]) + params[N-1]) + params[N])         
    // 즉, (((params[1] op params[2]) op ...) op params[N]) 으로 전개 됩니다.        
    return (...+ params); 
}   
// UnaryLeftFold_17 과 동일하게 전개되나 마지막에 init 을 추가합니다.
template<typename... Params>
int BinaryLeftFold_17(int init, Params... params) {
    // ((나머지 전개) + params[N]) 
    // (((나머지 전개) + params[N-1]) + params[N]) 
    // ((((나머지 전개) + params[N-2]) + params[N-1]) + params[N])         
    // 즉, (((init op params[1]) op ...) op params[N]) 으로 전개 됩니다.        
    return (init +...+ params); 
}

EXPECT_TRUE(UnaryRightFold_17(1, 2, 3) == 1 + 2 + 3);
EXPECT_TRUE(BinaryRightFold_17(10, 1, 2, 3) == 1 + 2 + 3 + 10);  

EXPECT_TRUE(UnaryLeftFold_17(1, 2, 3) == 1 + 2 + 3);
EXPECT_TRUE(BinaryLeftFold_17(10, 1, 2, 3) == 10 + 1 + 2 + 3); 

태그:

카테고리:

업데이트:

댓글남기기