타입들에 대해 여러가지 연산을 수행할 수 있는 메타 함수들을 제공하고 있다.
static_assert(bool 타입의 constexpr)
전달된 식이 참이라면 컴파일러에 의해 해당 식은 무시되고 거짓이라면 해당 문장에서 컴파일 오류 발생시킴
v를 static 인자로 가지는 클래스 -> 어떠한 값을 static 객체로 가지고 있는 클래스를 만들어주는 템플릿
클래스와 공용체에서만 사용가능
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 << "\n";
std::cout << a.*p_n;
return 0;
}
컴파일 오류 발생 대신 함수의 오버로딩 후보군에서만 제외된다.
template<bool B, class T = void>
struct enable_if;
B가 참일때 enable_if::value의 타입이 T가 되고
B가 거짓일때 value가 존재하지 않게 된다.
C++에서 타입에 대한 정보를 컴파일 타임에 알아내고 조작하려면, '값(value)'을 '타입(type)'으로 승격시키는 작업이 필요합니다.
std::integral_constant<T, v>: 변수 v(값)를 템플릿 인자로 받아 하나의 독립된 타입으로 만들어주는 래퍼(Wrapper)입니다.
템플릿 메타프로그래밍에서는 런타임의 if (bool) 대신, 컴파일 타임의 타입 매칭을 통해 분기를 태워야 합니다. 그래서 integral_constant<bool, true>를 std::true_type으로, false를 std::false_type으로 미리 정의해 둡니다.
<type_traits> 헤더에 있는 수많은 메타 함수들(예: is_pointer, is_arithmetic)은 조건을 만족하면 true_type을, 아니면 false_type을 상속받도록 구현되어 있습니다.
템플릿은 아무 타입이나 다 받아주기 때문에 잘못된 타입이 들어오면 에러 메시지가 걷잡을 수 없이 길어집니다.
컴파일 타임의 assert입니다. 주로 type_traits와 결합하여 입력된 타입이 내가 원하는 조건이 맞는지 입구 컷을 담당합니다.
예를 들어, 네트워크 패킷이나 게임 세이브 데이터를 고속으로 복사(memcpy)하는 템플릿 함수를 만들 때, static_assert(std::is_trivially_copyable_v, "안전하게 복사할 수 없는 타입입니다!");라고 선언하여 치명적인 런타임 메모리 버그를 컴파일 타임에 차단합니다.
일반 포인터가 메모리의 특정 '절대 주소'를 가리킨다면, 멤버 포인터는 클래스 시작점으로부터의 '상대적 위치(Offset)'를 가리킵니다.
객체가 아직 생성되지 않았어도(인스턴스가 없어도), 클래스 내부에 어떤 변수들이 있는지를 가리킬 수 있습니다. 사용할 때는 반드시 실제 인스턴스(a.p_n)나 포인터(pa->p_n)가 필요합니다.
엔진의 리플렉션(Reflection) 시스템이나 인스펙터(UI) 창을 구현할 때 핵심이 됩니다. "Player 클래스의 hp 변수 위치"를 멤버 포인터로 맵핑해두면, 나중에 어떤 Player 인스턴스가 들어오든 범용적인 코드로 hp 값을 읽고 쓸 수 있습니다.
이 모든 것을 조합하여 컴파일러가 알아서 알맞은 함수를 선택하게 만드는 마법입니다.
컴파일러가 템플릿 타입(T)을 추론하여 끼워 넣다가 문법적으로 말이 안 되는 상황이 발생하면, 에러를 내뿜고 죽는 대신 "어? 이 함수는 아니네? 다른 오버로딩 함수 찾아볼까?" 하고 쿨하게 넘어가는 규칙입니다.
SFINAE 규칙을 우리가 원하는 대로 조종하기 위한 스위치입니다.
조건 B(주로 type_traits의 결과)가 참(true)이면: 내부에 type이라는 멤버(T)가 생성됩니다. (치환 성공 -> 함수 후보 등록)
조건 B가 거짓(false)이면: 내부에 type이 아예 정의되지 않습니다. 컴파일러가 ::type을 찾으려다 실패하므로 SFINAE가 발동합니다. (치환 실패 -> 함수 후보에서 조용히 탈락)