SFINAE 는 Substitution failure is not an error 의 줄임말로, 대체 실패는 오류가 아니라는 것을 의미한다.
즉, 템플릿 변수에 들어갈 수 없는 자료형이나 값이 들어가도 오류가 발생하지 않는 상황을 말한다.
예를 들어, 아래에 오버로드된 함수가 있다.
int negative(int i)
template <typename T> typename T::value_type negative(const T& t)
int negative(int i) { return -i; }
template <typename T>
typename T::value_type negative(const T& t) {
return -T(t);
}
이때 int 10 을 매개 변수로 넘기자. negative(10)
, 컴파일러는 이름이 negative
로 되어있는 함수들을 훑어본다. 모두 훑어본 뒤 어떤 함수를 실행할지 결정한다.
훑어보면서 제네릭 형태로 되어있는 T 를 int 형으로 바꿔놓는다
. 그러면 다음과 같이 된다.
template <typename int>
int::value_type negative(const int& t) {
/* ... */
}
가만히 보면 이상함을 느낄 수 있다. int
는 value_type
이라는 멤버가 없기 때문이다! 다시 말해, 잘못된 코드가 된다.
컴파일러는 negative
로 되어있는 함수를 훑어보며 위와 같은 코드를 생성한다.
그 후, 부적합한 int::value_type negative(const int& t)
는 무시하고, 가장 적합한 int negative(int i)
를 선택해서 사용한다.
그렇다면 컴파일러는 잘못된 코드에 대해서 컴파일 오류 메세지를 출력할까? 출력하지 않는다. 단지 해당 코드를 선택하지 않을 뿐이다.
이를 Substitution Failure Is Not An Error 혹은 줄여서 SFINAE 라고 한다.
enable_if 는 SFINAE 로 인한 잘못된 코드가 생성되지 않게 한다.
위에서 설명한 SFINAE 와 연관지어 설명하면, int 자료형이 매개 변수로 들어왔을 때 int::value_type negative(const int& t)
로 치환 자체를 안한다.
그렇다면 어떻게 치환을 할지 말지 결정할까?
is_integral 함수를 통해, 매개 변수로 넘어온 자료형이 사용 가능한지 아닌지 확인하고 true false 를 반환하며 결정한다.
아래와 같이 구현되어있다.
// 1.
template <bool, typename T = void>
struct enable_if {};
// 2.
template <typename T>
struct enable_if<true, T> {
typedef T type;
};
2 에서 enable_if<true, T>
는 enable_if 로 true 와 T 자료형이 들어오면 T 를 type 으로 이름을 바꿔 사용하겠다고 선언한다. 그러면서 후보군에 넣는다. 하지만 false 가 들어오게 되면 false 에 맞게 오버로딩된 함수가 없기 때문에 1 번으로 가며, T 는 void 가 되고 type 도 없게 된다.
negative
함수를 예시로 설명을 더 해보자.
// 1.
template <bool, typename T = void>
struct enable_if {};
// 2.
template <typename T>
struct enable_if<true, T> {
typedef T type;
};
int negative(int i) { return -i; }
// template <typename T>
// T::value_type negative(const int& t) {
// /* ... */
// }
template <typename T, typename std::enable_if<!std::is_integral<T>::value, T>::type* = nullptr>
T::value_type negative(const int& t) {
/* ... */
}
치환 오류를 발생하던 코드를 주석하고, enable_if
가 포함된 함수를 만들었다. is_integral
은 자료형 T
를 받은 후, integral
범위인지 아닌지 확인하여 true
또는 flase
를 반환한다.
위 템플릿 함수는 integral
자료형이 들어왔을 때, int::value_type negative(const int& t)
의 형태로 치환되면 안되기 때문에 !
를 붙여서 <!std::is_integral< T > ~ > 로 구현했다.
int 10 을 매개변수로 넘겨보자. int 자료형이 들어와서 is_integral
이 true
를 반환하면 enable_if
는 typename std::enable_if<!(true), T>
가 된다. 즉 false
, enable_if<false, void>
가 되고, 이에 맞게 오버로딩 된 함수가 없기 때문에 컴파일러는 치환하지 않는다.
이렇게 함으로써 잘못 치환된 코드들을 생성하지 않으면서, 잠재적인 위험을 막을 수 있다!