신기한 C++문법들을 탐방하던 도중 이런 주제를 목격했다.
그 주제는 런타임중 해당 인스턴스의 멤버변수가 존재하는지 알 수 있는가? 에 대한 주제였다.
ㄹㅇ 개궁금하지 아니한가? 플머라면 못 참는 주제다.. 바로 들어가봤다.
c++ - 클래스에 특정 멤버 변수가 있는지 감지하는 방법은 무엇입니까? - 스택 오버플로 (stackoverflow.com)
외국 형님들이 엄청나게 길게 설명해주셨다.
실제로 코드를 복사해서 붙여넣으니까 동작하는거임 ㅁㅊ 대체 왜 되는거임?
아래 링크가 외국 성님들이 짜주신 코드다...
#include <type_traits>
template<typename T, typename = void>
struct has_id : std::false_type { };
template<typename T>
struct has_id<T, decltype(std::declval<T>().id, void())> : std::true_type { };
#include <iostream>
struct X { int id; };
struct Y { int foo; };
int main()
{
std::cout << std::boolalpha;
std::cout << has_id<X>::value << std::endl;
std::cout << has_id<Y>::value << std::endl;
}
이거 때문에 해보지도 않은 메타프로그래밍을 처음 접하게 되었다....
이 코드를 해석하면서 배운 중요한 개념이 있는데,
첫 번째로, decltype, std::declval
두 번째로, SFINAE(Substitution Failure Is Not An Error) 기법
세 번째로, 원래 좀 알고 있긴 했지만 애매하게 알고있던 템플릿 특수화
네 번째로, 콤마 연산자(중요한건 모르겠고 이런걸 처음 봄)
이렇게 4개정도 되는듯?
이 4개를 여기에 다 설명하면 블로그 글을 500만줄을 써야하므로 생략하겠습니다.. 보고오시길 ㅠ
제 누추한 실력으로 알아본 간략한 원리 및 흐름을 위 코드를 통해 설명해 보자면...
has_id라는 기본 템플릿 구조체와 특수화 템플릿 구조체, 즉 두개를 만들어준다.
decltype(std::declval<T>().id, void()) 을 통해 멤버변수가 있는지 판별한다. 원리는 다음과 같다.
id가 있는 경우 : declval가 타입추론에 성공하면서, 콤마 연산자를 통해 추론값을 버리고 decltype은 void 자료형을 추론하게 된다.(여기서 declval을 쓰는 이유가 있는데, 이 글을 참고하면 좋다.) 자료형 추론에 성공하며 템플릿 두번 째 인자가 정상적으로 대입되어 특수화 템플릿 구조체가 실행된다. 이 경우 std::true_type을 상속받으므로 value값은 true가 된다.
id가 없는 경우 : declval이 타입추론에 실패하면서 컴파일 에러가 나야하지만, 이 경우에 신기하게도 특수화 템플릿을 무시하고 유효한 인자가 있는 기본 템플릿이 사용된다.(이 기법이 SFINAE기법이라고 하네요?) 이 경우 std::false_type을 상속받으므로 value값은 false가 된다.
위 코드를 보고 공부한 것을 토대로 내 입맛대로 변경해봤다.
람다는 솔직히 쓸 일 없을 줄 알고 공부안했는데, 이럴 때 쓰더라.....
그래서 GPT쌤의 도움을 좀 받았는데, 더 공부해야 될 것 같다.....
#include <iostream>
#include <type_traits>
// 매크로로 멤버 존재 여부를 체크하는 유틸리티 매크로화
#define CREATE_MEMBER_CHECK(member) \
namespace CheckMember { \
template<typename T, typename = void> \
struct has_##member : std::false_type {}; \
\
template<typename T> \
struct has_##member<T, decltype(std::declval<T>().member, void())> \
: std::true_type {}; \
} \
// 객체와 멤버 이름을 받아서 멤버 존재 여부를 검사하는 람다
#define HAS_MEMBER(instance, member) \
[] (const auto& obj) -> bool { \
bool has_member = CheckMember::has_##member<decltype(obj)>::value; \
return has_member; \
} (instance) \
// idX, idY 멤버를 가진 구조체 생성
struct X { int idX; };
struct Y { int idY; };
struct XY { int idX; int idY; };
// idX, idY 멤버가 있는지 검사하는 구조체 생성
CREATE_MEMBER_CHECK(idX)
CREATE_MEMBER_CHECK(idY)
int main()
{
X x;
Y y;
XY xy;
std::cout << std::boolalpha; // bool을 true, false로 출력시키는 기능
std::cout << "x has idX : " << HAS_MEMBER(x, idX) << std::endl;
std::cout << "x has idY : " << HAS_MEMBER(x, idY) << std::endl;
std::cout << "y has idX : " << HAS_MEMBER(y, idX) << std::endl;
std::cout << "y has idY : " << HAS_MEMBER(y, idY) << std::endl;
std::cout << "xy has idX : " << HAS_MEMBER(xy, idX) << std::endl;
std::cout << "xy has idY : " << HAS_MEMBER(xy, idY) << std::endl;
}
오늘은 제가 메타프로그래밍이란 것을 처음 접해봤는데요?
사실 템플릿을 이용한 흑마법이라고 밖에 생각이 안듬 ㅋㅋ 그냥 광기의 영역 ㅇㅇ.....
진짜 공부하면서 알아갈때마다 충격의 연속이었읍니다. 처음보는 문법에... 이걸 이렇게 쓸 생각을 한다는거에...
근데 사실 이걸 쓸 일이 있을까? 싶다요?
여러분들도 미치지 말고 오래 삽시다!