C++ 아이콘 제작자: Darius Dan - Flaticon
함수는 아니지만 마치 함수처럼 작동하는 템플릿 클래스를 템플릿 함수라고 한다.
메타? 보통함수는 값에 대해 연산을 하지만 메타 함수는 타입에 대해 연산을 한다.
==> 실제로 함수가 아니라 ()호출 하지 않고 <>에 타입을 전달하여 작업을 한다.
예시
template <typename T>
struct is_void { static constexpr bool value = false; };
template <>
struct is_void<void> { static constexpr bool value = true; };
template <typename T>
void tell_type() {
if (is_void<T>::value) { std::cout << "T 는 void ! \n"; }
else { std::cout << "T 는 void 가 아니다. \n"; }
}
int main() {
tell_type<int>(); // void 아님!
tell_type<void>(); // void! }
1개는 모든 타입 T에 대해 매칭되는 템플릿과 void에 특수화된 템플릿을 만들어
()가 아닌 <> 통해 전달된 함수가 void타입 인지 아닌지 판별하는 템플릿 메타 함수이다.
타입이 void면 is_void<>::value 값이 ture가 되고 반대이면 false가 된다.
C++11 부터 type_traits 헤더 파일에 여러 템플릿 메타 함수를 지원한다.
아래 몇가지 살펴보자 (정확한 내용은 type_traits )
class A {};
template <typename T> // 정수 타입만 받는 함수
void only_integer(const T& t) {
static_assert(std::is_integral<T>::value);
std::cout << "T is an integer \n";
}
int main() {
int n = 3;
only_integer(n);
A a;
only_integer(a); // 컴파일 오류 발생
}
- bool타입의 constexpr만 static_assert로 확인 가능하고 그 외의 경우 컴파일 오류가 난다.
- 조건 뒤에 메세지를 넣으면 조건이 거짓이라면 해당 메세지를 출력하고 컴파일 오류 발생
- 참이라면 그냥 해당 식이 없는 듯 로직이 실행됨.
is_integral<>::value
표현 대신 사용할 수 있다.emplate <class T>
inline constexpr bool is_integral_v = is_integral<T>::value; // C++ 17 부터 사용 가능.
namespace detail
{
template<class T>
std::integral_constant<bool, !std::is_union<T>::value> test(int T::*);
template<class>
std::false_type test(...);
}
template<class T>
struct is_class : decltype(detail::test<T>(nullptr)) {};
위 처럼 정의되어 있다. is_class
std::integral_constant<T, T v> 로 정의되어 있는데, 그냥 v 를 static 인자로 가지는 클래스이다.
<> 안에 있는 is_union로 공용체를 확인한다.
그러면 공용체가 아니면 !로 인해 ture값을 가지는 test(int T::*) 가 생긴다.
class A {
public:
int n;
A(int n) : n(n) {}
};
int main() {
int A::* p_n = &A::n;
A a(3);
std::cout << "a.n : " << a.n << std::endl;
std::cout << "a.*p_n : " << a.*p_n << std::endl;
}
근데 p_n이 실제 존재하는 어떤한 객체의 int형 멤버를 가리키는 것이 아니다.
그럼 어떻게 사용??
int A::*p_n = &A::n; 처럼 정의했기에 p_n을 역참조해서 해당 객체의 멤버에 접근한다.
a.*p_n
그런데 해당 문법은 클래스에만 사용할 수 있다.
참고로 해당 클래스에 가리키고자 하는 포인터의 타입인 멤버 변수가 없어도 유효한 문법이다.
다만 아무것도 가리킬 수 없을 뿐...
하지만 여기에선 그냥 클래스인지 아닌지 판별하기 위해 사용하는 거라 상관이 없다.
공용체를 전달하면 false를 가지는 클래스가 생성됨
- 클래스, 공용체가 아닌 타입을 전달받으면 test 2개 중 첫번째는 데이터 멤버를 가리키는포인터를 사용하므로 클래스 또는 공용체가 아니라면 올바르지 않은 문법이다.
- 그래서 오버로딩 후보에서 제외되어 2번째 test가 오버로딩되어 false값을 가지게 된다.
흔히 줄여서 SFINAE 라는 원칙 때문에 템플릿 인자 치환 후에 만들어진 식이 문법적으로 틀렸다면
컴파일 오류가 아닌 그냥 오버로딩 후보군에서 제외된다.여기서 템플릿 인자 치환시 함수 내용이 문법적으로 올바른지 확인하는게 아니다.
==> 단순히 함수의 인자와 리턴 타입만 확인한다.그래서 인자와 리턴 타입이 맞고 함수 내용이 문법적으로 올바르지 않다면 컴파일 오류가 발생한다.
ex) 해당 타입의 멤버 변수에 접근하는 함수에서 인자와 리턴 타입이 맞지만
해당 클래스에 없는 변수에 접근
그런데 SFINAE을 통해 조건에 맞지 않는 함수들을 후보군에서 제외시켜주는 간단한 함수가 있다.
template <bool B, class T = void>
struct enable_if {}; // true가 아니라면 아무것도 정의 안됨
template <class T>
struct enable_if<true, T> { // B가 true일 때 특수화된 버전으로 type이 정의됨
typedef T type;
}; // 그래서 true일 때만 std::integral<>::value가 올바른 문장이 되서 잘 작동함
- 해당 정의인데 B에 확인하고 싶은 조건을 전달한다.
- ture이면 enable_if::value 의 타입이 T 가 되고
- 거짓이라면 enable_if 에 value가 존재하지 않는다
template <typename T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
void test(const T& t) { std::cout << "t : " << t << std::endl; }
int main() {
test(1); // int 1출력
test(false); // bool 0출력
test('c'); // char c출력
}
조건이 살짝 복잡해 보이는데
std::integral<>::value
가 참 일 때에만std::enable_if
에 value 가 정의되어서 위 코드가 컴파일 오류를 발생시키지 않는다.typename = 은 디폭트 인자를 전달하는 부분인데 원래는 typename x = 이런식 이지만
그냥 식의 값이 없으면 컴파일 오류발생 식이 있으면 그냥 함수 실행
==> 식만 있으면 되기에 딱히 인자를 정의를 안함.
std::enable_if<>::type은 의존 타입이여서 typename을 앞에 붙였다.
std::is_integral<>::value>::type
작성이 귀찮고 복잡하면 사용하자template <bool B, class T = void>
using enable_if_t = typename enable_if<B, T>::type; // C++ 14 부터 사용 가능
type_traits
static_assert
is_class
is_integral
std::false_type
enable_if
공부한 내용 복습
개인 공부 기록용 블로그입니다.
틀린 부분 있으다면 지적해주시면 감사하겠습니다!!