고급 템플릿 프로그래밍
1. SFINAE, enable_if
SFINAE (Substitution Failure Is Not An Error)는 템플릿 메타프로그래밍에서 중요한 개념으로, 템플릿 인자에 따라 컴파일 타임에 조건을 처리할 수 있도록 합니다. `enable_if`는 특정 조건에 맞는 타입만을 허용하는 데 사용됩니다.
1.1. SFINAE
템플릿을 사용할 때 조건에 맞지 않으면 컴파일 오류를 발생시키는 대신, 해당 템플릿을 제외시킵니다. 이를 통해 오버로딩을 사용할 때 더욱 유연하게 템플릿을 설계할 수 있습니다.
#include <iostream>
#include <type_traits>
template <typename T>
std::enable_if_t<std::is_integral<T>::value, T> add(T a, T b) {
return a + b;
}
int main() {
std::cout << add(3, 4) << std::endl; // int형 덧셈
// std::cout << add(3.14, 2.71) << std::endl; // 컴파일 오류 (float는 허용되지 않음)
return 0;
}
1.2. enable_if
`enable_if`는 조건이 만족될 때만 템플릿을 활성화하도록 도와줍니다. 이로 인해 조건에 맞는 타입만을 허용하게 할 수 있습니다.
#include <iostream>
#include <type_traits>
template <typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type add(T a, T b) {
return a + b;
}
int main() {
std::cout << add(5, 10) << std::endl; // int형 덧셈
return 0;
}
2. 템플릿 메타프로그래밍
템플릿 메타프로그래밍(TMP)은 템플릿을 사용하여 컴파일 타임에 계산을 수행하는 기법입니다. 이를 통해 런타임에 계산을 지연시키거나 최적화할 수 있습니다.
2.1. 컴파일 타임 계산
템플릿 메타프로그래밍을 사용하면 컴파일 타임에 계산을 수행할 수 있습니다. 예를 들어 팩토리얼 값을 계산하는 템플릿을 만들어 볼 수 있습니다.
#include <iostream>
template <int N>
struct factorial {
static constexpr int value = N * factorial<N - 1>::value;
};
template <>
struct factorial<0> {
static constexpr int value = 1;
};
int main() {
std::cout << factorial<5>::value << std::endl; // 120
return 0;
}
3. type_traits, std::variant, std::any
`type_traits`, `std::variant`, `std::any`는 고급 템플릿 프로그래밍에서 매우 유용한 도구들입니다. `type_traits`는 타입에 대한 정보를 제공하며, `std::variant`와 `std::any`는 타입 안전성을 보장하는 데 유용합니다.
3.1. type_traits
`type_traits`는 타입에 대한 특성을 컴파일 타임에 검사할 수 있게 해주는 유용한 템플릿 도구입니다. 예를 들어, 타입이 정수형인지 확인할 수 있습니다.
#include <iostream>
#include <type_traits>
template <typename T>
void printIfIntegral(T t) {
if constexpr (std::is_integral<T>::value) {
std::cout << t << std::endl;
} else {
std::cout << "Not an integral type!" << std::endl;
}
}
int main() {
printIfIntegral(42); // 출력: 42
printIfIntegral(3.14); // 출력: Not an integral type!
return 0;
}
3.2. std::variant
`std::variant`는 여러 타입을 하나의 변수로 다룰 수 있는 안전한 방법을 제공합니다. `std::variant`는 타입이 확실히 하나일 때만 값을 가지며, 여러 타입을 안전하게 다룰 수 있습니다.
#include <iostream>
#include <variant>
std::variant<int, double, std::string> getData(int choice) {
if (choice == 0) return 42;
else if (choice == 1) return 3.14;
else return "Hello";
}
int main() {
auto data = getData(1);
std::visit([](auto&& arg){ std::cout << arg << std::endl; }, data);
return 0;
}
3.3. std::any
`std::any`는 어떤 타입이든 저장할 수 있는 컨테이너입니다. 이를 통해 타입을 명시적으로 다루지 않고도 다양한 타입의 값을 저장할 수 있습니다.
#include <iostream>
#include <any>
int main() {
std::any a = 42;
std::cout << std::any_cast<int>(a) << std::endl; // 출력: 42
a = std::string("Hello, C++!");
std::cout << std::any_cast<std::string>(a) << std::endl; // 출력: Hello, C++!
return 0;
}