가변 인자 템플릿
Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 104개 중 67번째.
어떤 타입의 인자라도 개수에 상관없이 받을 수 있는 함수가 필요하다면 어떻게 해야 할까요? 전통적인 템플릿은 필요한 타입 매개변수의 개수를 정확히 지정해야 합니다. 가변 인자 템플릿(Variadic templates)은 템플릿이 임의의 개수의 템플릿 인자를 받을 수 있도록 허용함으로써 이 문제를 해결합니다.
구문은 ...(생략 부호)를 사용하여 파라미터 팩을 생성합니다:
template <typename... Args>
void printAll(Args... args) {
// Args는 템플릿 파라미터 팩입니다
// args는 함수 파라미터 팩입니다
}인수를 처리하기 위해 일반적으로 기저 사례(base case)가 있는 재귀를 사용합니다. 파라미터 팩은 인수가 남지 않을 때까지 한 번에 하나씩 확장됩니다:
// 기저 사례: 남은 인수가 없음
void print() {
std::cout << std::endl;
}
// 재귀 사례: 첫 번째 인수를 처리하고 나머지에 대해 재귀 호출
template <typename T, typename... Rest>
void print(T first, Rest... rest) {
std::cout << first << " ";
print(rest...); // 남은 인수 확장
}
print(1, 3.14, "hello", 'x'); // 출력: 1 3.14 hello x명시적인 재귀 없이 더 간단한 연산을 위해 폴드 표현식(C++17)을 사용할 수도 있습니다:
template <typename... Args>
auto sum(Args... args) {
return (args + ...); // 폴드 표현식: 모든 인수를 더합니다
}
std::cout << sum(1, 2, 3, 4) << std::endl; // 출력: 10가변 인자 템플릿은 std::make_unique, std::tuple, 그리고 std::function과 같은 많은 표준 라이브러리 기능을 지원합니다. 이들은 어떠한 인자 유형 및 개수의 조합과도 작동하는 타입 안전(type-safe) 함수를 가능하게 합니다.
챌린지
쉬움임의의 개수의 인수를 가진 메시지를 처리하기 위해 가변 인자 템플릿(variadic templates)을 사용하는 유연한 로깅 시스템을 구축해 보겠습니다. 파라미터 팩(parameter packs)을 사용하여 값을 연결하고, 인수의 개수를 세고, 형식화된 출력을 인쇄할 수 있는 유틸리티를 만들 것입니다.
코드는 다음 두 개의 파일로 구성됩니다.
Variadic.h: 여기에 가변 인자 템플릿 함수를 정의합니다.모든 인수를 공백으로 구분하여 출력하고 마지막에 줄바꿈을 출력하는
printAll함수를 만드세요. 줄바꿈만 출력하는 기본 케이스(base case)와 첫 번째 인수를 출력하고 공백을 넣은 뒤 나머지 인수로 재귀 호출하는 재귀 케이스(recursive case)를 사용하는 재귀적 접근 방식을 사용하세요.전달된 인수의 개수를 반환하는
countArgs함수를 만드세요. 파라미터 팩의 크기를 얻으려면sizeof...를 사용하세요.모든 숫자 인수를 더하고 그 결과를 반환하는
sum함수를 만드세요.+연산자와 함께 폴드 표현식(fold expression)을 사용하세요.모든 숫자 인수를 곱하고 그 결과를 반환하는
product함수를 만드세요.*연산자와 함께 폴드 표현식을 사용하세요.main.cpp: 네 개의 입력(각각 별도의 줄에 있음)을 읽습니다.- 정수 (integer)
- 실수 (double)
- 문자열 (string)
- 문자 (character)
다음과 같이 가변 인자 템플릿을 시연하세요.
- 네 개의 모든 입력을 사용하여
printAll을 호출하고 결과를 출력합니다. - 정수, 실수, 문자를 사용하여
countArgs를 호출할 때 인수의 개수를 출력합니다:Argument count: <count> - 정수, 실수, 그리고 값 10과 5의 합계를 계산한 후 출력합니다:
Sum: <result> - 정수와 값 2와 3의 곱을 계산한 후 출력합니다:
Product: <result> - 문자열만 사용하여(단일 인수)
printAll을 호출합니다. - 인수 없이
printAll을 호출합니다 (줄바꿈만 출력되어야 합니다).
예를 들어, 입력이 5, 2.5, Hello, X인 경우:
5 2.5 Hello X
Argument count: 3
Sum: 22.5
Product: 30
Hello
printAll이 4개에서 0개까지 다양한 타입의 인수를 어떻게 처리하는지 확인해 보세요. sum과 product 함수는 폴드 표현식을 사용하여 명시적인 재귀 없이 모든 값을 우아하게 결합합니다. countArgs 함수는 sizeof...가 컴파일 타임에 팩 크기를 어떻게 제공하는지 보여줍니다.
치트 시트
가변 인자 템플릿(Variadic templates)을 사용하면 ... (생략 부호) 구문을 사용하여 템플릿이 임의의 개수의 템플릿 인자를 받을 수 있게 하며, 이를 통해 파라미터 팩(parameter pack)을 생성할 수 있습니다:
template <typename... Args>
void printAll(Args... args) {
// Args는 템플릿 파라미터 팩입니다
// args는 함수 파라미터 팩입니다
}기본 케이스(base case)가 있는 재귀를 사용하여 인자를 처리합니다:
// 기본 케이스: 남은 인자가 없음
void print() {
std::cout << std::endl;
}
// 재귀 케이스: 첫 번째 인자를 처리하고 나머지에 대해 재귀 호출
template <typename T, typename... Rest>
void print(T first, Rest... rest) {
std::cout << first << " ";
print(rest...); // 남은 인자들을 확장함
}
print(1, 3.14, "hello", 'x'); // 출력: 1 3.14 hello xsizeof...를 사용하여 파라미터 팩의 인자 개수를 가져옵니다:
template <typename... Args>
size_t countArgs(Args... args) {
return sizeof...(args);
}명시적인 재귀 없이 더 간단한 연산을 수행하려면 폴드 표현식(fold expressions) (C++17)을 사용하세요:
template <typename... Args>
auto sum(Args... args) {
return (args + ...); // 폴드 표현식: 모든 인자를 더함
}
std::cout << sum(1, 2, 3, 4) << std::endl; // 출력: 10폴드 표현식은 곱셈과 같은 다른 연산자와도 함께 작동합니다:
template <typename... Args>
auto product(Args... args) {
return (args * ...); // 모든 인자를 곱함
}직접 해보기
#include <iostream>
#include <string>
#include "Variadic.h"
using namespace std;
int main() {
// 입력 읽기
int intVal;
double doubleVal;
string strVal;
char charVal;
cin >> intVal;
cin >> doubleVal;
cin >> strVal;
cin >> charVal;
// TODO: 4개의 모든 입력을 사용하여 printAll 호출
// TODO: intVal, doubleVal, charVal을 사용하여 countArgs로 인자 개수 출력
// 형식: "Argument count: <count>"
// TODO: intVal, doubleVal, 10, 5의 합계를 계산하고 출력
// 형식: "Sum: <result>"
// TODO: intVal, 2, 3의 곱을 계산하고 출력
// 형식: "Product: <result>"
// TODO: 문자열만 사용하여 printAll 호출
// TODO: 인자 없이 printAll 호출
return 0;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.