Type Traits

Sven van Huessen | Nov 4, 2025

Type Traits

In the previous blog post about SFINAE I explained how we can use std::enable_if_t to enable/disable a function based on some property.

In that example I used std::is_integral_v and std::is_floating_point_v from the standard library to know if a type is an integer or floating-point. These utilities are called type traits. The standard library already offers quite some type traits that you can use to check certain things. For example:

  • std::is_integral - check if type is integer.
  • std::is_floating_point - checks if type is float/double.
  • std::is_pointer - check if type is a pointer
  • std::is_array - check if type is an array
  • std::is_const - checks if type is const-qualified
  • std::is_same - checks if 2 types are the same
  • std::is_base_of - checks inheritance relationship
  • etc…

But there is nothing stopping us from making our own type traits.

For ImReflect, I wanted an implementation that only accepts smart pointers. To do this I needed a way of checking if a type is one of the three smart pointers: std::shared_ptr, std::unique_ptr, std::weak_ptr.

We first need to implement a default implementation/fallback:

template<typename T>
struct is_smart_pointer : std::false_type {};

With this, you can call is_smart_pointer<TYPE>::value where TYPE can be anything.

is_smart_pointer<int>::value; // false
is_smart_pointer<bool>::value; // false
is_smart_pointer<char>::value; // false
is_smart_pointer<std::shared_ptr<int>>::value; // false
is_smart_pointer<std::unique_ptr<int>>::value; // false
is_smart_pointer<std::weak_ptr<int>>::value; // false

Right now, no matter what T is, it will always return false. Even when T is a smart pointer… To solve this, we can add a specialization. Whenever you specify a specialization, the compiler will pick that one, instead of the generic templated one:

template<typename T>
struct is_smart_pointer<std::shared_ptr<T>> : std::true_type {};
template<typename T>
struct is_smart_pointer<std::unique_ptr<T>> : std::true_type {};
template<typename T>
struct is_smart_pointer<std::weak_ptr<T>> : std::true_type {};

Now whenever I call is_smart_pointer with one of the three smart pointers, it will find the specialization and return true when I call ::value.

is_smart_pointer<int>::value; // false
is_smart_pointer<bool>::value; // false
is_smart_pointer<char>::value; // false
is_smart_pointer<std::shared_ptr<int>>::value; // true
is_smart_pointer<std::unique_ptr<int>>::value; // true
is_smart_pointer<std::weak_ptr<int>>::value; // true

Just like SFINAE, type traits are super useful when working with templates and meta programming. They allow us to check for certain properties of types at compile time.

With this type trait, we can now make a function for ImReflect that uses the type trait:

template<typename T>
std::enable_if_t<is_smart_pointer_v<T>::value> tag_invoke(Detail::ImInputLib_t, const char* label, T& value) {
    // T is guarenteed to be one of the 3 smart pointers
}