C++에서는 Type Traits라는 라이브러리를 사용할 수 있다.
Type Traits은 타입의 속성을 컴파일 타임에 검사하거나 수정하는 기능을 가진 라이브러리를 말한다.
템플릿을 사용해 컴파일 타임에 타입을 검사 혹은 수정하기 때문에
정수 타입만을 받아 나눗셈을 수행하는 함수를 구현한다고 했을 때, 다음과 같이 템플릿 특수화를 이용할 수도 있지만...
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>_v
는 is_integral<T>::value
를 편하게 쓰기 위한 헬퍼 변수이다.
C++17부터 사용 가능하며 다음과 같이 나타낼 수 있다.
template< class T >
constexpr bool is_integral_v = is_integral<T>::value;
🟡다음과 같은 타입 검사 기능들이 있다.
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
타입 검사에서는 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)