codenuri 강석민 강사 강의 내용기반으로 정리한 내용입니다.
SFINAE
함수 찾는 순서 1. exactly matching
#include <iostream>
using namespace std;
template<typename T>
void foo(T t) {cout << "T" << endl;}
void foo(int t) {cout << "int" << endl;}
void foo(...) {cout << "..." << endl;}
int main()
{
foo(3); // int 호출
}
함수 찾는 순서 2. template
#include <iostream>
using namespace std;
template<typename T>
void foo(T t) {cout << "T" << endl;}
// void foo(int t) {cout << "int" << endl;}
void foo(...) {cout << "..." << endl;}
int main()
{
foo(3); // temlate 호출
}
함수 찾는 순서 3. variable argument
#include <iostream>
using namespace std;
template<typename T>
// void foo(T t) {cout << "T" << endl;}
// void foo(int t) {cout << "int" << endl;}
void foo(...) {cout << "..." << endl;}
int main()
{
foo(3); // ... 호출
}
… 과 template 이 같이 있을 때 template 을 사용함!
SFINAE 개념
return 타입을 int로 한다고 해도 여전히 템플릿을 사용할 것임
#include <iostream>
using namespace std;
template<typename T>
int foo(T t) {cout << "T" << endl; return 0;}
void foo(...) {cout << "..." << endl;}
int main()
{
foo(3);
}
T가 int 로 결정되어 함수를 만들어 내려고 하는데 int 안에 type이 없어(int::type foo(int t)) error 일 것임
컴파일 에러가 나올지 가변인자를 사용할 것인지?
C++에서는 에러가 아니라 가변인자를 부른다.
#include <iostream>
using namespace std;
template<typename T>
typename T::type foo(T t) {cout << "T" << endl; return 0;}
void foo(...) {cout << "..." << endl;}
int main()
{
foo(3);
}
- Substitution Failure Is Not An Error
- 함수 템플릿을 사용 시 T의 타입이 결정되고 함수를 생성(instantiation) 하려고 할 때 리턴 타입이나 함수 인자 등에서 치환에 실패하면 컴파일 에러가 아니라, 함수 후보군에서 제외한다.
- 동일한 이름의 다른 함수가 있다면 다른 함수를 사용하게 한다.
enable_if
- C++ 표준에서 지원하는 도구
- 1번째 인자가 true 일 경우만 type이 정의 된다.
template<bool b, typename T = void> strut enable_if
{
}
template<typename T> struct enable_if<true, T>
{
typedef T type;
}
int main()
{
enable_if<true, int>::type t0; // int
enable_if<true>::type t1; // void
enable_if<false, int>::type t3; // error. type이 없다.
enable_if<false>::type t4; // error. type이 없다.
}
enable_if 를 활용하는 코드
#include <iostream>
#include <type_traits>
using namespace std;
// 정수일 때만 함수 코드를 생성하고 싶다.
template<typename T> void foo(T a)
{
static_assert(is_integral<T>::value, "error");
}
void foo(...)
{
cout << "not integer" << endl;
}
int main()
{
foo(3.4);
}
정수가 아닐 때 다른 함수에게 기회를 주고 싶다.
#include <iostream>
#include <type_traits>
using namespace std;
template<typename T>
typename enable_if<is_integral<T>::value>::type
foo(T a)
{
cout << "T" << endl;
}
void foo(...)
{
cout << "not integer" << endl;
}
int main()
{
foo(3);
foo(3.4);
}
정수가 아닐 때 치환에 실패하고 후보군에서 제거되어 가변인자인 foo를 부름(“not integer”)
조건을 만족할 때 만 함수를생성하는 방법
- static_assert: 조건을 만족하지 않으면 컴파일 에러
- enable_if: 조건을 만족하지 않으면 함수를 생성하지 않음.
- 동일 이름의 다른 함수가 있다면 사용.
enable_if 위치
- 함수 리턴 타입
- 함수 인자 타입 -> 생성자에서 주로 사용
- 템플릿 인자 -> 함수 자체의 모양이 단순해 보이는 장점이 있다.
#include <iostream>
#include <type_traits>
using namespace std;
// 1
template<typename T>
typename enable_if<is_integral<T>::value>::type
foo(T a)
{
cout << "T" << endl;
}
// 2
template<typename T>
void foo(T a, typename enable_if<is_integral<T>::value>::type* = nullptr)
{
cout << "T" << endl;
}
// 3
template<typename T, typename enable_if<is_integral<T>::value>::type* = nullptr>
void foo(T a)
{
cout << "T" << endl;
}
void foo(...)
{
cout << "not integer" << endl;
}
int main()
{
foo(3);
foo(3.4);
}
enable_if vs integral_constant
타입의 종류에 따라 다르게 동작하는 함수 만드는 방법
- type_traits(is_pointer) + if constexpr
- type_traits + 함수 오버로딩(false_type, true_type)
- type_traits + enable_if(요즘 많이 쓰임)
#include<iostream>
#include<type_traits>
using namespace std;
template<typename T>
void printv_imp(const T& v, true_type)
{
cout << v << " : " << *v << endl;
}
template<typename T>
void printv_imp(const T& v, false_type)
{
cout << v << endl;
}
template<typename T> void prev_printv(const T& v)
{
/*
// 1.
// C++17
if constexpr (is_pointer<T>::value)
{
cout << v << " : " << *v << endl;
} else {
cout << v << endl;
}
*/
// 2.
// print_imp(v, is_pointer<T>());
}
// 3.
template<typename T>
typename enable_if<is_pointer<T>::value>::type printv(const T& v)
{
cout << v << " : " << *v << endl;
}
template<typename T>
typename enable_if<!is_pointer<T>::value>::type printv(const T& v)
{
cout << v << endl;
}
int main()
{
int n = 0;
printv(n);
printv(&n);
}