NeuroWhAI의 잡블로그

[C++] std::void_t를 이용해 클래스에 특정 멤버가 있는지 확인하기. 본문

개발 및 공부/언어

[C++] std::void_t를 이용해 클래스에 특정 멤버가 있는지 확인하기.

NeuroWhAI 2018. 8. 5. 21:07


※ 아래 내용은 스택오버플로의 글을 공부하고 정리한 내용이므로 틀린 내용이 있을 수 있습니다.



템플릿 메타프로그래밍을 이용해서 특정 타입에 특정 멤버가 있는지 컴파일 타임에 확인할 수 있는 방법이 있습니다.

일단 코드부터 보시죠.

#include <iostream>
#include <type_traits>

template<typename, typename = void>
struct has_member_foo : std::false_type { };

template<typename T>
struct has_member_foo<T, std::void_t<decltype(std::declval<T>().foo)>>
    : std::true_type { };

class Bar
{
public:
    int bar;
};

class Foo
{
public:
    explicit Foo(int);
    int foo;
};

int main()
{
    using namespace std;
    
    cout << std::boolalpha
        << has_member_foo<int>::value << " "
        << has_member_foo<Bar>::value << " "
        << has_member_foo<Foo>::value << endl;

    return 0;
}

타입에 foo 멤버변수가 있는지 확인하는 예제입니다.

결과는 false false true입니다.


동작 원리를 한번 천천히 보겠습니다.


T를 int라고 가정합니다.

먼저 컴파일러는 has_member_foo<int>를 보고 has_member_foo의 정의로 갑니다.

보니 has_member_foo는 두 템플릿 파라미터를 받는데 지금 주어진건 int 하나뿐이므로 두번째 파라미터에 설정된 기본 타입인 void를 사용합니다.

현 시점에서 has_member_foo<int>는 has_member_foo<int, void>가 됩니다.


일단 여기까지 성공적이고 이제 has_member_foo<int, void>의 특수화 버전이 있는지 찾아봐야 합니다.

has_member_foo의 특수화 버전을 보려고 검색하니

has_member_foo<T, std::void_t<decltype(std::declval<T>().foo)>> 요놈이 있긴한데 두번째 파라미터가 뭔지 계산이 필요해보입니다.


때어내서 std::void_t<decltype(std::declval<T>().foo)> 이것만 봅시다.

std::declval<T>는 T&를 반환하는 녀석입니다.

T에 기본 생성자가 없어도 T&를 얻을 수 있고 여기서 T의 멤버도 얻을 수 있죠.

그래서 대충 (&T).foo가 되는데 T가 뭐랬죠? int잖아요.

int에 foo가 있나요?

없죠. 컴파일러도 그 사실을 여기서 알게됩니다.

그럼 컴파일 에러를 띄울까요?


아닙니다.

SFINAE라는 규칙이 있는데 템플릿 특수화를 찾는 과정에서 이런 에러가 발생해도 컴파일 에러를 띄우지 않는다는 겁니다.

에러를 띄우지 않는 대신 에러가 난 특수화 버전을 무시하게 되죠.

고로 남은건 처음의 녀석, std::false_type를 상속하는 녀석뿐입니다.

컴파일러는 해당 버전을 선택하고 has_member_foo<int>::value는 false가 됩니다.


이번엔 T를 Foo라고 가정합니다.

has_member_foo<Foo, void>가 되는 것 외엔 요놈의 특수화 버전을 찾는 부분까진 이전과 동일합니다.

특수화 버전인 has_member_foo<T, std::void_t<decltype(std::declval<T>().foo)>>에서

T를 Foo랑 일단 매치시킬 수 있으니

has_member_foo<Foo, std::void_t<decltype(std::declval<T>().foo)>> 이렇게 됩니다.


아까처럼 std::void_t<decltype(std::declval<T>().foo)>만 때어내서 봅시다.

Foo엔 foo멤버가 있으므로 (&Foo).foo에서 에러가 발생하지 않습니다.

decltype(x)는 x의 타입을 얻는 기능을 합니다. 여기선 foo가 int형이므로 int를 뱉겠지만 그건 중요하지 않습니다.

정리하면 이제 std::void_t<int>가 되고 void_t는 그냥 뭐가 들어오던 void 하나로 바꿔주는 놈이므로

has_member_foo<Foo, std::void_t<decltype(std::declval<T>().foo)>>는

has_member_foo<Foo, void>가 됩니다.


오! 매치되는 특수화 버전을 찾았습니다.

이 버전은 std::true_type를 상속받고 있으므로

has_member_foo<Foo>::value는 true가 됩니다.


이런 과정으로 특정 타입에 특정한 멤버가 있는지 확인할 수 있게 되었습니다!

사실 그렇게 쓸만한 기능은 아닐 수 있지만 위 과정을 이해하고 응용할 수 있게 된다면

더 복잡한 템플릿 메타프로그래밍도 넘어서실 수 있을거라 봅니다!



Comments