템플릿 메타 함수, type_traits(is_integer, is_class, enable_if)

하루공부·2024년 1월 21일
0

C++

목록 보기
18/25
post-thumbnail

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가 된다.



type_traits

  • C++11 부터 type_traits 헤더 파일에 여러 템플릿 메타 함수를 지원한다.

  • 아래 몇가지 살펴보자 (정확한 내용은 type_traits )



is_integer

  • 정수 타입인지 확인
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); // 컴파일 오류 발생
}
  • static_assert는 C++11에 추가된 키워드로 인자로 전달된 식이 참인지 아닌지 컴파일 타임에 확인한다
    • bool타입의 constexpr만 static_assert로 확인 가능하고 그 외의 경우 컴파일 오류가 난다.
    • 조건 뒤에 메세지를 넣으면 조건이 거짓이라면 해당 메세지를 출력하고 컴파일 오류 발생
    • 참이라면 그냥 해당 식이 없는 듯 로직이 실행됨.

  • is_integral_v, is_integral<>::value 표현 대신 사용할 수 있다.
emplate <class T>
inline constexpr bool is_integral_v = is_integral<T>::value; // C++ 17 부터 사용 가능.


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)) {};

위 처럼 정의되어 있다. is_class


  • 우선 integral_constant??

    std::integral_constant<T, T v> 로 정의되어 있는데, 그냥 v 를 static 인자로 가지는 클래스이다.
    <> 안에 있는 is_union로 공용체를 확인한다.
    그러면 공용체가 아니면 !로 인해 ture값을 가지는 test(int T::*) 가 생긴다.


데이터 멤버를 가리키는포인터 (Pointer to Data member)

  • int T::* 라는 문법은 처음 본다.
  • 이는 T의 int멤버를 가리키는 포인터라는 의미다. 아래 예시
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;
}
  • int A::* p_n은 A의 int형 멤버를 가리킬 수 있는 포인터를 의미

    근데 p_n이 실제 존재하는 어떤한 객체의 int형 멤버를 가리키는 것이 아니다.


  • 그럼 어떻게 사용??

    int A::*p_n = &A::n; 처럼 정의했기에 p_n을 역참조해서 해당 객체의 멤버에 접근한다.

    a.*p_n

  • 그런데 해당 문법은 클래스에만 사용할 수 있다.

    참고로 해당 클래스에 가리키고자 하는 포인터의 타입인 멤버 변수가 없어도 유효한 문법이다.

    다만 아무것도 가리킬 수 없을 뿐...
    하지만 여기에선 그냥 클래스인지 아닌지 판별하기 위해 사용하는 거라 상관이 없다.


  • 그래서 다시 is_class로 돌아와서 클래스를 넣으면 integral_constant로 인해 static true를 인자로 가지는 클래스가 생성된다.

    공용체를 전달하면 false를 가지는 클래스가 생성됨


  • std::false_type는 integral_constant 특수화된 것으로 false값을 저장한다.
    • 클래스, 공용체가 아닌 타입을 전달받으면 test 2개 중 첫번째는 데이터 멤버를 가리키는포인터를 사용하므로 클래스 또는 공용체가 아니라면 올바르지 않은 문법이다.
    • 그래서 오버로딩 후보에서 제외되어 2번째 test가 오버로딩되어 false값을 가지게 된다.


치환 오류는 컴파일 오류가 아니다 (Substitution failure is not an error - SFINAE

  • 올바르지 않은 문법이여서 오보로딩 후부에서 제외되었는데
    템플릿 인자로 받은 타입을 치환하여 인스턴스화 하는데 문법적으로 틀린 경우가 있다.
    ==> 문법이 틀려서 오류를 발생하지 않는다

    흔히 줄여서 SFINAE 라는 원칙 때문에 템플릿 인자 치환 후에 만들어진 식이 문법적으로 틀렸다면
    컴파일 오류가 아닌 그냥 오버로딩 후보군에서 제외된다.

    여기서 템플릿 인자 치환시 함수 내용이 문법적으로 올바른지 확인하는게 아니다.
    ==> 단순히 함수의 인자와 리턴 타입만 확인한다.

    그래서 인자와 리턴 타입이 맞고 함수 내용이 문법적으로 올바르지 않다면 컴파일 오류가 발생한다.

    ex) 해당 타입의 멤버 변수에 접근하는 함수에서 인자와 리턴 타입이 맞지만
    해당 클래스에 없는 변수에 접근


  • 이것을 활용하여 원하는 타입만 오버로딩될 수 있게 만들 수 있다.

    그런데 SFINAE을 통해 조건에 맞지 않는 함수들을 후보군에서 제외시켜주는 간단한 함수가 있다.



enable_if

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을 앞에 붙였다.


  • enable_if_t, 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
공부한 내용 복습

개인 공부 기록용 블로그입니다.
틀린 부분 있으다면 지적해주시면 감사하겠습니다!!

0개의 댓글