3 분 소요

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 형태를 처리함

그 외의 문법에 대해서 알아보면

  1. static : instance를 생성하지 않고 struct를 함수처럼 사용할 것이기 때문에 static을 사용
  2. 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)) {};

코드설명

Image

class일 경우 데이터 맴버를 가르키는 포인터로 인해서 템플릿이 첫번째 함수로 구현된다. (인자값이 구체적이라서 우선순위가 높음)
class가 아닐경우 우선순위가 낮은 가변인자 템플릿쪽으로 구현된다
integral_constant의 경우 bool과 true 또는 false 값을 가지고 있는 구조체이기 때문에 해당 구조체를 상속한 is_class도 bool값을 가지고 있다
따라서 is_class::value 를 검사하면 클래스인지 아닌지 구분 가능

is_union?

해당 템플릿 메타함수의 경우 c++단에서는 구현이 안되고 컴파일러 단에서 구현되어 있음

       

4. enable_if

Template SFINAE참조

타입이 아닌 맴버 함수를 검사하는 Template

declvalenable_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(...)에서 voidvoid_t를 넣음과 동시에 타입체킹도 해서 코드가 더 안전해짐

댓글남기기