[C++] Type Traits

dumdumer·2024년 10월 16일
0

C++

목록 보기
5/5

Type Traits

C++에서는 Type Traits라는 라이브러리를 사용할 수 있다.

Type Traits은 타입의 속성을 컴파일 타임에 검사하거나 수정하는 기능을 가진 라이브러리를 말한다.

템플릿을 사용해 컴파일 타임에 타입을 검사 혹은 수정하기 때문에

  1. 타입에 따라 다른 동작을 수행하도록 할 수 있다.
  2. 잘못된 타입 사용으로 발생하는 오류를 런타임 이전에 잡을 수 있다.

🟡예시

정수 타입만을 받아 나눗셈을 수행하는 함수를 구현한다고 했을 때, 다음과 같이 템플릿 특수화를 이용할 수도 있지만...

template<typename T>
T integral_div(T a, T b) {
	return a / b;
}

// int 특수화
template<>
int integral_div<int>(int a, int b) {
	return a / b;
}
// char 특수화
template<>
char integral_div<char>(char a, char b) {
	return a / b;
}

// 다른 정수형 타입에 대해 전부 특수화 진행

다음과 같이 type traits 라이브러리를 사용해 정수 타입인지를 컴파일 타임에 검사할 수도 있다.

# include <type_traits> // <-- std type traits library

template<typename T>
T integral_div(T a, T b) {
	static_assert(std::is_integral<T>::value,
		"integral_div accepts only integral types");
	return a / b;
}

🟡 is_integral<T>is_integral<T>_v

위 예시에서 is_integral<T>static constexpr 형의 value값을 가진 구조체이다.

정수형 타입이라면 value는 true를, 그렇지 않다면 false 값을 가진다.

is_integral<T>외에도 이와 비슷해보이는 is_integral<T>_v가 있다.

is_integral<T>_vis_integral<T>::value를 편하게 쓰기 위한 헬퍼 변수이다.
C++17부터 사용 가능하며 다음과 같이 나타낼 수 있다.

template< class T >
constexpr bool is_integral_v = is_integral<T>::value;

타입 검사에 관한 Type Traits 기능들

🟡다음과 같은 타입 검사 기능들이 있다.

is_floating_point // 부동소수점형(float, double)
is_arithmetic     // 산술타입(정수형 + 부동소수점형)
is_signed         // signed 타입
is_unsigned       // unsigned 타입 
is_enum           // enum 타입(enum, enum class)
is_void           // void 타입
is_pointer        // 포인터 타입(T*)
is_null_pointer   // nullptr (C++)

🟡참조, 함수, 배열에 대한 타입 검사

is_reference // 참조(T&)
is_array     // 배열인지 검사
is_function  // 함수 타입
void foo(int a, int b) {}

int a = 10;
int& b = a;
cout << is_reference<decltype(b)>::value; // true
cout << is_array<int[]>::value; // true
cout << is_function<decltype(foo)>::value; // true

🟡클래스에 대한 검사

is_class // 클래스인지 검사(struct, class)
is_abstract // 추상 클래스인지 검사(적어도 하나의 순수가상함수가 포함되는지)
is_polymorphic // 다형성 클래스인지 검사(적어도 하나의 가상함수가 포함되는지)
struct A{};
class B {
	virtual void foo() {}
};
class C {
	virtual void bar() = 0;
};

cout << is_class<A>::value; // true
cout << is_polymorphic<B>::value; // true
cout << is_abstract<C>::value; // true

🟡타입의 속성 검사

is_const // 타입이 `const` 타입인지 검사

주의

template<typename T>
void f(T x) { cout << std::is_const_v<T>; }

template<typename T>
void g(T& x) { cout << std::is_const_v<T>; }

template<typename T>
void h(T& x) {
	cout << std::is_const_v<T>;
	x = nullptr;
}

int main() {
	const int a = 3;
	f(a); // false. const가 값전달 되면서 사라짐!
	g(a); // true
	const int* b = new int;
	h(b); // false. T: (const int)*
}

🟡타입 간의 관계 검사

is_same<T, R> // T와 R이 같은 타입인지 검사
is_base_of<T, R> // T가 R의 베이스 클래스인지 검사
is_convertible<T, R> // T가 R로 형변환 될 수 있는지 검사
class B {};
class C : B {};

cout << is_same<int, unsigned int>::value; // false
cout << is_base_of<B, C>::value; // true
cout << is_convertible<int, float>::value; // true

타입 조작에 관한 Type Traits 기능들

타입 검사에서는 value 값을 통해 검사를 확인할 수 있었다.

타입 조작(Type Manipulation)에서는 type필드를 통해 어떤 타입을 다른 타입으로 조작할 수 있다.

is_integral<T>::value == is_integral_v<T>와 비슷하게, 타입 조작에서도 make_unsigned<T>::type == make_unsigned_t<T>로 사용할 수 있다.

🟡기능 종류들

// signed, unsigned 조작
make_signed // signed 타입으로 조작
make_unsigned // unsigned 타입으로 조작

// 포인터와 레퍼런스 조작
remove_pointer // 포인터를 제거(T* -> T)
remove_reference // 참조를 제거(T& -> T)
add_pointer // 포인터로 변경(T -> T*)
add_reference // 참조로 변경(T -> T&)

// const 조작
remove_const // const 제거(const T -> T)
add_const // const로 변경

// 이외의 타입 조작
common_type<T, R> // T와 R의 공통인 타입을 반환. ex. <int, short> --> int
conditional<pred, T, R> // pred 식이 true이면 T, false이면 R을 반환
decay<T> // 함수 인자에 값으로 전달될 때와 같은 타입을 리턴한다.

🟡예시

// 배열의 포인터를 지우고 해당 타입의 값으로 배열 첫번째 인덱스를 가져옴.
template<typename T>
void f(T ptr) {
	using R = remove_pointer_t<T>;
	R x = ptr[0];
	cout << x;
}

// 전달된 인수의 타입에 해당하는 const 타입 변수 생성
template<typename T>
void g(T x) {
	using R = add_const_t<T>;
	R y = 3;
	// y = 4; // 변경할 수 없음
}

// 두 값을 더한 후 T와 R 중 공통인 타입으로 반환
template<typename T, typename R>
common_type_t<T, R> add(T x, R y) {
	return x + y;
}

// T와 R 중 타입 크기가 더 큰 쪽으로 형변환하여 더한 값을 반환
template<typename T, typename R>
auto h(T a, R b) {
	constexpr bool pred = sizeof(T) > sizeof(R);
	using S = std::conditional_t<pred, T, R>;
	return static_cast<S>(a) + static_cast<S>(b);
}

int main()
{
	auto x = h(2, 'a');
	cout << x << ": " << typeid(x).name() << "\n"; // print "99: int"
	auto y = h(2, 2ull);
	cout << y << ": " << typeid(y).name() << "\n"; // print "4 : unsigned __int64" 즉 unsigned long long
	auto z = h(2.0f, 2ull); 
	cout << z << ": " << typeid(z).name() << "\n"; // print "4 : unsigned __int64" 즉 unsigned long long
	
	char a[] = "abc";
	f(a); // print 'a'
	g(2);
	
	using res_t = decltype(add(2, 3.0f));
	res_t b = add(2, 3.0f);
	cout << b << ": " << typeid(b).name(); // print "5: float"
}

.
.
.
C++ 공부를 위해 작성된 글입니다. 오류가 있다면 지적해 주시면 감사하겠습니다.

참고자료
https://en.cppreference.com/w/cpp/types/is_integral
https://github.com/federico-busato/Modern-CPP-Programming (10.Templates_I)

profile
tik tok

0개의 댓글