NeuroWhAI의 잡블로그
[C++] std::void_t를 이용해 클래스에 특정 멤버가 있는지 확인하기. 본문
※ 아래 내용은 스택오버플로의 글을 공부하고 정리한 내용이므로 틀린 내용이 있을 수 있습니다.
템플릿 메타프로그래밍을 이용해서 특정 타입에 특정 멤버가 있는지 컴파일 타임에 확인할 수 있는 방법이 있습니다.
일단 코드부터 보시죠.
#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가 됩니다.
이런 과정으로 특정 타입에 특정한 멤버가 있는지 확인할 수 있게 되었습니다!
사실 그렇게 쓸만한 기능은 아닐 수 있지만 위 과정을 이해하고 응용할 수 있게 된다면
더 복잡한 템플릿 메타프로그래밍도 넘어서실 수 있을거라 봅니다!
'개발 및 공부 > 언어' 카테고리의 다른 글
[C++] lock tag(defer_lock, try_to_lock, adopt_lock) 설명 + scoped_lock (0) | 2018.08.26 |
---|---|
[Rust] in/exclusive range expression and pattern (0) | 2018.08.25 |
[C++] 기본 인수(Default arguments) 고급 (0) | 2018.08.04 |
[Kotlin] 헬로, 코틀린! (0) | 2018.08.04 |
[Rust] Auto-Dereferencing Rules (자동 역참조 규칙) (0) | 2018.07.27 |
Comments