함수나 메서드가 인자의 수를 미리 알지 못하고, 호출 시에 전달되는 인자의 수가 가변적인 경우에 사용되는 개념입니다.
즉, 함수가 호출될 때 필요한 인자의 개수를 정확히 알 수 없는 경우에 인자를 받아들이는 방식입니다.
...로 표현되며, 그리스어에서 따온 단어로 ellipsis(생략)라고 부릅니다.
C++98에서 가변 인자를 처리하는 데 사용되는 타입이며, cstdarg 헤더 파일에 정의된 여러 매크로 함수들을 함께 사용합니다.
cstdarg 매크로 함수
va_start
void va_start(std::va_list ap, parm_n);
: 가변 인자의 목록을 처리하기 위해 초기화하는 매크로입니다. 가변 인자의 첫 번째 인자를 처리하는 데 사용됩니다.
- ap : 원어로는 'argument pointer'로, va_list 타입의 변수로, 가변 인자 목록을 추적하는 데 사용됩니다.
- parm_n : n개 parameter를 의미하며, 가변 인자 목록의 첫 번째 고정 인자를 나타내는 변수입니다.
va_arg
type va_arg(std::va_list ap, type);
: 가변 인자 목록에서 값을 하나 씩 꺼내는 매크로입니다.
- ap : 원어로는 'argument pointer'로, va_list 타입의 변수입니다.
- type : T는 추출하려는 인자의 타입을 나타냅니다. va_arg는 이 타입을 매개변수로 받아 가변 인자 리스트에서 해당 타입의 인자를 반환합니다. 이때 타입 T는 정확히 지정해야 합니다.
va_copy
void va_copy(std::va_list dest, std::va_list src);
: va_copy는 가변 인자 목록을 다른 va_list 변수에 복사할 때 사용됩니다.
va_list는 포인터처럼 동작하고, 한 번 va_arg로 인자를 읽어가면 그 상태는 소모되므로, 원본을 그대로 두고 복사본에서 다른 방식으로 인자를 읽어야 할 때 va_copy가 필요합니다.
- dest : 원어로는 'destination'으로, 초기화할 va_list 타입의 변수입니다.
- src : 원어로는 'source'로, 초기화에 사용될 va_list 타입의 변수입니다.
va_end
void va_end(va_list ap);
: 가변 인자 목록을 처리한 후 종료할 떄 호출하는 매크로입니다.
- ap : 원어로는 'argument pointer'로, 정리할 va_list 타입의 변수입니다.
<example> | 출력 1
#include <iostream>
#include <cstdarg>
using namespace std;
void print(int count, ...) {
va_list args;
va_start(args, count);
for (int i = 0; i < count; ++i) {
int num = va_arg(args, int);
cout << num << " ";
}
va_end(args);
cout << endl;
}
int main() {
print(3, 10, 20, 30); // 3개의 인자 전달
print(5, 1, 2, 3, 4, 5); // 5개 인자 전달
return 0;
}
결과값
C++11에서 도입된 템플릿 기능으로, 임의의 수의 인수를 지원하는 클래스 또는 함수 템플릿을 작성할 수 있게 해줍니다.
즉, 템플릿을 정의할 때 인자 수를 고정하지 않고 여러 개의 인자를 받을 수 있는 기능을 제공하여 유연성을 제공합니다.
parameter pack
템플릿에서 인자의 개수가 동적으로 결정되는 경우에 사용됩니다. C++11 이후 도입된 이 기능은 하나 이상의 템플릿 매개변수를 패킹(packing)하여 함수나 클래스로 전달할 수 있습니다. 가변 인자 템플릿을 통해 여러 개의 인자를 처리할 수 있습니다.
- 템플릿 파라미터 팩(template parameter pack) : 템플릿을 사용할 때, 하나의 템플릿 파라미터가 여러 개의 인수를 받을 수 있게 해주는 기능입니다. 즉, 템플릿 파라미터 팩을 사용하면 템플릿이 여러 인수로 확장될 수 있습니다.
template<typename... Arguments> class classname;
- 함수 파라미터 팩(function parameter pack) : 함수 템플릿에서 함수가 여러 개의 인수를 받을 수 있도록 하는 기능입니다. 즉, 이 방식은 함수가 다양한 인수 개수와 타입을 처리할 수 있게 해줍니다.
template<typename... Arguments> void functionName(Arguments... arguments);
- 람다 초기화 캡처 팩(lambda init-capture pack) : C++20에 구현된 기능으로, 초기화자(initializer)에서 팩 확장(pack expansion)을 사용하여 여러 변수들을 초기화 캡처하는 방식입니다.
auto lambda = [init_capture1, init_capture2, ...](parameter_pack) { };
<example> | 출력 2
#include <iostream>
using namespace std;
// 재귀 종료 조건 : 하나의 인자만 남았을 때
template<typename T>
void print(T value) {
cout << value << " " << endl;
}
// 재귀적 케이스 : 두 개 이상의 인자가 남았을 때
template<typename T, typename... Args> // function parameter pack
void print(T val, Args... args) { // Argument Pack
cout << val << " ";
print(args...); // Argument Pack Expansion
}
int main() {
print(10, 20, 30);
print(1, 2, 3, 4, 5);
return 0;
}
결과값
Fold expression을 사용하지 않고, 가변 인자 템플릿을 사용할 때는 재귀 탈출 조건을 반드시 설정해야 합니다.
C++17에서 도입된 기능으로, 이진 연산자에 대한 팩을 Reduces(또는 folds)합니다.
특히 템플릿 매개변수 팩(parameter pack)을 처리할 때 유용하게 사용됩니다.
cast는 단항 연산으로 높은 우선 순위를 가지지만, 단항 연산 중에선 가장 낮은 우선순위를 가집니다.
즉, 후위 증감 연산자와 전위 증감 연산자 다음 우선순위를 가집니다.
[ op ( op ( op ))]
<example>
#include <iostream>
using namespace std;
template <typename... Args>
int UnaryRightFold(Args... args) {
return (args - ...);
}
int main() {
cout << UnaryRightFold(1, 2, 3, 4);
return 0;
}
1 - (2 - (3 - 4)) = 1 - (2 - (-1)) = 1 - 3 = -2
결과값
[(( op ) op ) op ]
<example>
#include <iostream>
using namespace std;
template <typename... Args>
int UnaryLeftFold(Args... args) {
return (... - args);
}
int main() {
cout << UnaryLeftFold(1, 2, 3, 4);
return 0;
}
((1 - 2) - 3) - 4 = (-1 -3) - 4 = -4 -4 = -8
결과값
[ op ( op ( op ( op )))]
동일한 op를 사용해야 합니다. 즉, 'pack - ... + init'는 사용이 불가능합니다.
<example>
#include <iostream>
using namespace std;
#define init 10
template <typename... Args>
int BinaryRightFold(Args... args) {
return (args - ... - init);
}
int main() {
cout << BinaryRightFold(1, 2, 3, 4);
return 0;
}
1 - (2 - (3 - (4 - 10))) = 1 - (2 - (3 - (-6))) = 1 - (2 - 9) = 1 - (-7) = 8
결과값
[((( op ) op ) op ) op ]
동일한 op를 사용해야 합니다. 즉, 'init + ... - pack'은 사용이 불가능합니다.
<example>
#include <iostream>
using namespace std;
#define init 10
template <typename... Args>
int BinaryLeftFold(Args... args) {
return (init - ... - args);
}
int main() {
cout << BinaryLeftFold(1, 2, 3, 4);
return 0;
}
(((10 - 1) - 2) - 3) - 4 = ((9 - 2) - 3) -4 = (7 - 3) - 4 = 4 - 4 = 0
결과값
<example> | 출력 3
#include <iostream>
using namespace std;
// Fold expression을 사용하여 여러 인자를 출력하는 함수
template<typename... Args>
void print(Args... args) {
((cout << args << " "), ...);
cout << endl;
}
int main() {
print(10, 20, 30);
print(1, 2, 3, 4, 5);
return 0;
}
결과값