C++에서 template 매개변수에 자료형이나 값을 넣을 수 없어도 오류가 발생하지 않는 상황을 말한다.
int negate(int i) { return -i; }
template <typename T>
typename T::value_type negate(const T& t) {
return -T(t);
}
negate(42) 의 경우 컴파일러는 첫 번째 오버로딩 후보를 선택하여 -42를 리턴할 것이다.
컴파일러가 아래의 후보를 확인하는 과정에서 템플릿 인자 T를 int로 추론하게 되고 다음과 같은 코드가 생성된다.
int::value_type negate(const int& t) {
/*...*/
}
위의 코드는 잘못된 코드이지만 오류 메시지를 뱉지 않는다.
템플릿 인자 치환에 실패할 경우 컴파일러는 이 오류를 무시하고, 오버로딩 후보에서 제외하면 된다.
template <typename T>
void negate(const T& t) {
typename T::value_type n = -t();
}
위와 같은 코드는 T::value_type 때문에 컴파일 오류가 발생한다.
위의 경우는 SFINAE가 적용되지 않는다. T::value_type은 함수 타입과 템플릿 인자의 immediate context 바깥에 있기 때문이다.
특정한 타입들에게만 작동하는 템플릿을 작성하고 싶다면 함수의 선언부에 올바르지 않은 타입을 넣어서 타입 치환 오류를 발생시켜야 한다.
SFINAE는 잘못된 template 선언이 사용되는것을 막기 위해 도입되었지만,
개발자들이 컴파일 시에 활용할 수 있는 검사 기법을 만들어냈다.
SFINAE는 template을 인스턴스화할때 템플릿 전달인자의 속성들을 정할 수 있도록 해준다.
SFINAE를 활용하는 툴 중 하나. enable_if 는 아래와 같이 정의 가능하다.
template <bool, typename T = void>
struct enable_if {};
template <typename T>
struct enable_if<true, T> {
typedef T type;
};
template <class T,
typename std::enable_if<std::is_integral<T>::value, T>::type* = nullptr>
void do_stuff(T &t) {
std::cout << "do stuff" << std::endl;
}
template <class T,
typename std::enable_if<std::is_class<T>::value, T>::type * = nullptr>
void do_stuff(T &t) {
// class
}
do_stuff(int 변수)와 같이 호출하게 된다면 컴파일러는 첫 번째 오버로딩을 고르게 된다.
std::is_integral가 참이기 때문이다.
두 번째 오버로딩은 후보군에서 제외된다. std::is_class가 false이므로 type이 정의되지 않은 일반적인 형태의 struct enable_if가 선택되어서 치환 오류가 발생하기 때문.