Type Traits
1. 템플릿 메타함수
템플릿 메타함수란?
함수는 아니지만 함수처럼 쓰이는 템플릿 클래스 보통 함수는 값을 연산하지만 템플릿 메타함수는 타입에 관해서 연산
type-trait 라이브러리는 많은 템플릿 메타함수를 포함하고 있다
2. is_void
예제코드
#include <iostream>
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!
}
void 형인지 검사할 수 있는 템플릿 메타함수
is_void 의 구현
template <typename T>
struct is_void
{
static constexpr bool value = false;
};
template <>
struct is_void<void>
{
static constexpr bool value = true;
};
템플릿 특수화를 통해서 마치 if문을 처리하는 것처럼 void 형태를 처리함
그 외의 문법에 대해서 알아보면
- static : instance를 생성하지 않고 struct를 함수처럼 사용할 것이기 때문에 static을 사용
- consexpr : 템플릿 자체가 compile time 에 연산, 또한 static const로서 해당 타입에서 가장 고유한 값인 것을 표시
3. integral_constant
단순히 하나의 값과 그리고 그 값에 대한 type을 저장
template<class T, T v>
struct integral_constant
{
static constexpr T value = v;
using value_type = T;
using type = integral_constant; // using injected-class-name
constexpr operator value_type() const noexcept { return value; }
constexpr value_type operator()() const noexcept { return value; } // since c++14
};
들어온 Type을 value_type
으로 저장
들어온 값을 value
로 저장한다
type
은 다른 연산을 할 때 편하게 하기 위해서 자기자신을 정의
4. is_class
들어온 타입이 class형식인지 아닌지를 검사해주는 템플릿 메타함수 사용법은 간단하지만 구현방법은 다소 어렵다
들어가기 전에 데이터 맴버를 가르키는 포인터(Pointer to Data member) 문법을 알아야 함
#include <iostream>
class A
{
public:
int value;
};
int main()
{
int A::* pValue = &A::value; //맴버를 가르키는 포인터
//인스턴스 생성 전에 포인터로 가르킬 수 있다!
A test;
test.value = 3;
std::cout << test.value << std::endl;
std::cout << test.*pValue << std::endl;
return 0;
}
해당 예시와 같이 클래스의 맴버를 가르킬 수 있는 문법이다
이걸 다음 템플릿 구현에서 사용할 것인데 중요한 것은 클래스에서만 사용가능한 문법이라는 점
is_class의 구현
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)) {};
코드설명
class일 경우 데이터 맴버를 가르키는 포인터로 인해서 템플릿이 첫번째 함수로 구현된다. (인자값이 구체적이라서 우선순위가 높음)
class가 아닐경우 우선순위가 낮은 가변인자 템플릿쪽으로 구현된다
integral_constant의 경우 bool과 true 또는 false 값을 가지고 있는 구조체이기 때문에 해당 구조체를 상속한 is_class도 bool값을 가지고 있다
따라서 is_class::value 를 검사하면 클래스인지 아닌지 구분 가능
is_union?
해당 템플릿 메타함수의 경우 c++단에서는 구현이 안되고 컴파일러 단에서 구현되어 있음
4. enable_if
타입이 아닌 맴버 함수를 검사하는 Template
declval 와 enable_if를 사용하면 특정 함수가 있는 지를 검사하는 Template을 만들 수 있다
#include <iostream>
#include <type_traits>
// T는 반드시 func() 맴버 함수를 가지고 있어야 한다.
template <typename T, typename = decltype(std::declval<T>().func())>
void test1(const T& t)
{
std::cout << "t.func() : " << t.func() << std::endl;
}
// T 는 반드시 정수 타입을 리턴하는 맴버 함수 func 을 가지고 있어야 한다.
template <typename T, typename = std::enable_if_t<std::is_integral_v<decltype(std::declval<T>().func())>>>
void test2(const T& t)
{
std::cout << "t.func() : " << t.func() << std::endl;
}
struct A
{
int func() const { return 1; }
};
struct B
{
char func() const { return 'a'; }
};
int main()
{
test1(A{});
test2(A{});
test2(B{});
}
`typename = ` 문법에 관해서
원래대로라면 typename type = ~~~
이러한 형식의 디폴트 템플릿 매개변수가 되는 것이 맞다
하지만 지금은 template에서 단순히 들어오는 타입체크로만 사용하고 있기 때문에 실제로 typename은 선언하지 않고 넘어가는 것
즉, 사용하지 않을 타입은 생략이 가능
5. void_t
void_t의 구현은 단순
template <class...>
using void_t = void;
가변길이 템플릿으로 뭐가 오든 void로 바꾸는 것이 void_t이다 이걸 어디에다 사용할까?
타입이 아닌 맴버 함수를 검사하는 Template에서 체크하는 함수가 여러개가 되면 굉장히 복잡해진다
예시코드 1
stl 의 여러 라이브러리는 begin()과 end() 함수를 가지고 있다 해당 함수들을 받는 print 함수를 구현하면 다음과 같다
template <typename Cont,
typename = decltype(std::declval<Cont>().begin()),
typename = decltype(std::declval<Cont>().end())>
void print(const Cont& container)
{ ... }
함수가 늘어날 수록 구현이 복잡해진다
이를 void_t
를 이용해서 단순화할 수 있다
예시코드 2
template <typename Cont, typename = std::void_t<
decltype(std::declval<Cont>().begin()),
decltype(std::declval<Cont>().end())>>
void print(const Cont& container)
{ ... }
하지만 typename =
으로 시작하는 구문은 결국 템플릿 디폴트 매개변수이므로 사용자의 실수로 값을 넣게되면
타입체킹을 건너뛰게 된다
int main()
{
// 위 print 는 오버로딩 후보군에서 제외되지 않음!
print<Bad, void>(Bad{});
}
이런 경우를 방지하기 위해 해당 부분을 return으로 값을 뺼 수 있다
예시코드3
template <typename Cont>
std::void_t<decltype(std::declval<Cont>().begin()),
decltype(std::declval<Cont>().end())>
print(const Cont& container)
void print(...)
에서 void
에 void_t
를 넣음과 동시에 타입체킹도 해서 코드가 더 안전해짐
댓글남기기