고급 템플릿 프로그래밍
1. SFINAE, enable_if
SFINAE (Substitution Failure Is Not An Error)는 C++ 템플릿 프로그래밍에서 매우 핵심적인 개념입니다. 이는 템플릿을 인스턴스화할 때 특정 조건이 충족되지 않으면 단순히 해당 템플릿을 무시하게 만드는 메커니즘입니다. `enable_if`는 SFINAE를 구현하는 도구로서, 특정 타입 조건을 만족할 때만 템플릿 함수를 활성화하도록 돕습니다.
1.1. SFINAE
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; // 7 출력됨
// std::cout << add(3.14, 2.71) << std::endl; // 컴파일 오류 발생
return 0;
}
1.2. enable_if
`std::enable_if`는 타입 조건이 true일 때만 해당 템플릿을 정의하도록 합니다. 이를 통해 함수 템플릿의 사용을 제어하고, 코드의 타입 안정성을 높일 수 있습니다.
#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; // 출력: 15
return 0;
}
2. 템플릿 메타프로그래밍
템플릿 메타프로그래밍(Template Metaprogramming)은 C++ 템플릿을 활용하여 컴파일 타임에 계산과 로직 처리를 수행하는 기법입니다. 이 기법은 성능을 향상시키고, 코드 최적화를 가능하게 해줍니다. 특히 컴파일 타임 상수 계산, 타입 선택, 조건 분기 등에 자주 사용됩니다.
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
고급 템플릿 프로그래밍에서 C++ 표준 라이브러리에서 제공하는 `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::visit`을 통해 안전하게 값에 접근할 수 있습니다.
#include <iostream>
#include <variant>
#include <string>
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(2);
std::visit([](auto&& arg) {
std::cout << arg << std::endl;
}, data);
return 0;
}
3.3. std::any
`std::any`는 모든 타입을 저장할 수 있는 타입 안정 컨테이너입니다. 저장된 값을 꺼낼 때는 `std::any_cast`를 사용하며, 타입이 다르면 예외가 발생합니다.
#include <iostream>
#include <any>
#include <string>
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;
}