varidic template
- C++에서도 파이썬의 출력문을 동일하게 구현할 수 있다.
- C++의 템플릿을 이용하면 임의의 개수의 인자를 받은 함수를 구현할 수 있다.
#include <iostream>
template<typename T>
void print(T arg)
{
std::cout << arg << std::endl;
}
template<typename T, typename... Types>
void print(T arg, Types... args)
{
std::cout << arg << ", ";
print(args...);
}
int main()
{
print(1, 3.1, "abc");
print(1, 2, 3, 4, 5, 6, 7);
}
output
1, 3.1, abc
1, 2, 3, 4, 5, 6, 7
- typename뒤에 ...으로 오는 것을 템플릿 파라미터 팩이라고 부른다.
- 템플릿 파라미터 팩의 경우 0개 이상의 템플릿 인자들을 나타낸다.
- 함수에 인자로 ...으로 오는 것을 함수 파라미터 팩이라고 부른다.
- 템플릿 파라미터 팩과 함수 파라미터 팩의 차이점
- 템플릿의 경우 앞에 ...
- 함수의 경우 뒤에 ...
- 파라미터 팩은 추론된 인자를 제외한 나머지 인자들을 나타내게 된다.
print(1, 3.1, "abc");
- 위 print 함수 호출의 경우
- 컴파일러가 두 개의 print 함수의 정의를 살펴보고 어느 것을 택할지 결정
- 첫 번째 print의 경우 인자로 단 1개만 받기 때문에 후보에서 제외
- 두 번째 print가 택해짐
- print의 첫 번째 인자는 1이므로 T는 int로 추론
- arg에는 1이 오게 된다.
- args에는 나머지 3.1과 "abc"가 오게 된다.
- 추론을 계속하다가 인자가 1개가 되었을 경우
template <typename T>
void print(T arg);
- 위 함수가 호출된다.
- C++ 규칙 상, 파라미터 팩이 없는 함수의 우선순위가 높기 때문
- 이 규칙 때문에 마지막에 endl이 출력될 수 있음
순서를 바꾸게 된다면?
- 두 print 함수의 위치를 바꿔서 쓴다면 컴파일 에러가 발생
- C++ 컴파일러는 함수를 컴파일 시, 자신의 앞에 정의되어 있는 함수들 밖에 보지 못함
- 따라서 void print(T arg, Types... args) 함수를 컴파일 할때
- void print(T arg) 함수가 존재하는지 모름
- 마지막에 print("abc")의 오버로딩을 찾을 때, 파라미터 팩이 있는 함수를 택하게 된다.
- 그 경우 그 함수 안에서 print()가 호출이 된다.
- 하지만 코드에서 print()함수를 정의하지 않았기 때문에 컴파일러가 함수를 찾을 수 없다고 오류를 출력함
임의의 개수의 문자열을 합치는 함수
- 가변 길이 템플릿을 활용한 예시로 임의의 길이의 문자열을 합쳐주는 함수가 있다.
- std::string에서는 아래와 같이 문자열을 합친다.
contcat = s1 + s2 + s3;
- 위 코드는 아래와 같은 함수 호출이 일어난다.
concat = s1.operator+(s2).operator+(s3);
- s2를 더할 때 메모리가 할당이되고, s3를 더할 때 메모리 할당이 또 한번 발생할 수 있는 문제가 있다.
- 따라서 합쳐진 문자열의 크기는 미리 알 수 있으니 한 번에 필요한 만큼 메모리를 할당 하는 것이 훨씬 낫다.
std::string concat;
concat.reverse(s1.size() + s2.size() + s3.size());
concat.append(s1);
concat.append(s2);
concat.append(s3);
- 위 코드를 통해 메모리 할당을 1번 진행하여 문자열을 합칠 수 있다.
- 이 작업을 도와주는 함수는 아래와 같이 만들 수 있다.
std::string concat = strCat(s1, "abc", s2, s3);
- 이때 가변 길이 템플릿을 통해 StrCat 함수가 임의의 개수의 인자를 받도록 할 수 있다.
#include <iostream>
#include <string>
template<typename String>
std::string strCat(const String& s)
{
return std::string(s);
}
template<typename String, typename... Strings>
std::string strCat(const String& s, Strings... strs)
{
return std::string(s) + StrCat(strs...);
}
- 위 구현은 임의의 개수의 문자열을 합칠 수 있지만 결과적으로 operator+의 도움을 받았다.
- 따라서 strCat의 인자가 5개라면 메모리 할당이 5번 일어나게 된다.
template <typename String, typename... Strings>
std::string strCat(const String& s, Strings... strs)
{
// 먼저 합쳐질 문자열의 총 길이를 구한다.
size_t total_size = getStringSize(s, strs...);
// reserve 를 통해 미리 공간을 할당해 놓는다.
std::string concat_str;
concat_str.reserve(total_size);
concat_str = s;
// concat_str 에 문자열들을 붙인다.
appendToString(&concat_str, strs...);
return concat_str;
}
- getStringSize()를 통해 합쳐진 문자열의 총 길이를 계산한 후 문자열을 합친다.
sizeof...
- sizeof 연산자는 인자의 크기를 리턴하지만 파라미터 팩에 sizeof...을 사용할 경우 전체 인자의 개수를 반환함
#include <iostream>
// 재귀 호출 종료를 위한 베이스 케이스
int sumAll() { return 0; }
template <typename... Ints>
int sumAll(int num, Ints... nums)
{
return num + sumAll(nums...);
}
template <typename... Ints>
double average(Ints... nums) {
return static_cast<double>(sumAll(nums...)) / sizeof...(nums);
}
- sumAll 함수는 전달된 인자들의 합을 반환한다.
- 한편, average 함수의 경우 전달된 전체 인자 개수로 합을 나눠야한다.
- 여기서 sizeof...이 사용된다.
- sizeof...에 파라미터 팩을 전달하면 nums에 해당하는 실제 인자 개수를 반환한다.
Fold Expression
- C++11에서 도입된 가변 길이 템플릿은 매우 편리하지만 재귀 함수 형태로 구성해야한다는 단점이 있다.
- 반드시 재귀 호출 종료를 위한 함수를 따로 만들어야 한다.
- C++17에 새로 도입된 Fold 형식을 사용하면 간단하게 표현할 수 있다.
#include <iostream>
template<typename... Ints>
int sumAll(Ints... nums)
{
return (... + nums);
}
int main()
{
std::cout << sumAll(1, 4, 2, 3, 10) << std::endl;
}
- (... + nums)가 Fold 형식으로 아래와 같이 컴파일러에서 해석된다.
- ((((1 + 4) + 2) + 3) + 10);
- 이 형태를 단항 좌측 Fold (Unary left Fold)라고 부른다.
- C++17에서 지원하는 Fold 방식의 종류는 4가지가 존재
- (E op ...) 단항 우측 Fold (E1 op (... op (En-1 op En)))
- (... op E) 단항 좌측 Fold (((E1 op E2) op ...) op En)
- (E op ... op I) 이항 우측 Fold (E1 op (... op (En-1 op (En op I))))
- (I op ... op E) 이항 좌측 Fold ((((I op E1) op E2) op ...) op En)
- 여기서 op 자리에는 대부분 이항 연산자들이 포함될 수 있다.
- 한가지 중요한 점은 Fold 식을 쓸 때 꼭 ()로 감싸줘야 한다는 것
출처
'C++' 카테고리의 다른 글
[C++] 형 변환 (0) | 2024.12.18 |
---|---|
[C++] 예외처리 (0) | 2024.12.18 |
[C++] 템플릿 (1) | 2024.12.17 |
[C++] 가상 상속 (0) | 2024.12.17 |
[C++] explicit와 mutable (0) | 2024.12.16 |