# Type Traits

# Standard type traits

The type_traits header contains a set of template classes and helpers to transform and check properties of types at compile-time.

These traits are typically used in templates to check for user errors, support generic programming, and allow for optimizations.

Most type traits are used to check if a type fulfils some criteria. These have the following form:

template <class T> struct is_foo;

If the template class is instantiated with a type which fulfils some criteria foo, then is_foo<T> inherits from std::integral_constant<bool,true> (a.k.a. std::true_type), otherwise it inherits from std::integral_constant<bool,false> (a.k.a. std::false_type). This gives the trait the following members:

# Constants

static constexpr bool value

true if T fulfils the criteria foo, false otherwise

# Functions

operator bool

Returns value

bool operator()

Returns value

# Types

Name Definition
value_type bool
type std::integral_constant<bool,value>

The trait can then be used in constructs such as static_assert (opens new window) or std::enable_if (opens new window). An example with std::is_pointer:

template <typename T>
void i_require_a_pointer (T t) {
    static_assert(std::is_pointer<T>::value, "T must be a pointer type");

//Overload for when T is not a pointer type
template <typename T>
typename std::enable_if<!std::is_pointer<T>::value>::type
does_something_special_with_pointer (T t) {
    //Do something boring

//Overload for when T is a pointer type
template <typename T>
typename std::enable_if<std::is_pointer<T>::value>::type 
does_something_special_with_pointer (T t) {
    //Do something special

There are also various traits which transform types, such as std::add_pointer and std::underlying_type. These traits generally expose a single type member type which contains the transformed type. For example, std::add_pointer<int>::type is int*.

# Type Properties

Type properties compare the modifiers that can be placed upon different variables. The usefulness of these type traits is not always obvious.

Note: The example below would only offer an improvement on a non-optimizing compiler. It is a simple a proof of concept, rather than complex example.

e.g. Fast divide by four.

template<typename T>
inline T FastDivideByFour(cont T &var) {
  // Will give an error if the inputted type is not an unsigned integral type.    
  static_assert(std::is_unsigned<T>::value && std::is_integral<T>::value,
    "This function is only designed for unsigned integral types.");
  return (var >> 2);

Is Constant:

This will evaluate as true when type is constant.

std::cout << std::is_const<const int>::value << "\n"; // Prints true.
std::cout << std::is_const<int>::value << "\n"; // Prints false.

Is Volatile:

This will evaluate as true when the type is volatile.

std::cout << std::is_volatile<static volatile int>::value << "\n"; // Prints true.
std::cout << std::is_const<const int>::value << "\n"; // Prints false.

Is signed:

This will evaluate as true for all signed types.

std::cout << std::is_signed<int>::value << "\n"; // Prints true.
std::cout << std::is_signed<float>::value << "\n"; // Prints true.
std::cout << std::is_signed<unsigned int>::value << "\n"; // Prints false.
std::cout << std::is_signed<uint8_t>::value << "\n"; // Prints false.

Is Unsigned:

Will evaluate as true for all unsigned types.

std::cout << std::is_unsigned<unsigned int>::value << "\n"; // Prints true.
std::cout << std::is_signed<uint8_t>::value << "\n"; // Prints true.
std::cout << std::is_unsigned<int>::value << "\n"; // Prints false.
std::cout << std::is_signed<float>::value << "\n"; // Prints false.

# Type relations with std::is_same<T, T>

The std::is_same<T, T> type relation is used to compare two types. It will evaluate as boolean, true if the types are the same and false if otherwise.


// Prints true on most x86 and x86_64 compilers.
std::cout << std::is_same<int, int32_t>::value << "\n";
// Prints false on all compilers.
std::cout << std::is_same<float, int>::value << "\n";
// Prints false on all compilers.
std::cout  << std::is_same<unsigned int, int>::value << "\n";

The std::is_same type relation will also work regardless of typedefs. This is actually demonstrated in the first example when comparing int == int32_t however this is not entirely clear.


// Prints true on all compilers.
typedef int MyType
std::cout << std::is_same<int, MyType>::value <<  "\n";

Using std::is_same to warn when improperly using a templated class or function.

When combined with a static assert the std::is_same template can be valuable tool in enforcing proper usage of templated classes and functions.

e.g. A function that only allows input from an int and a choice of two structs.

#include <type_traits>
struct foo {
  int member;
  // Other variables

struct bar {
  char member;

template<typename T>
int AddStructMember(T var1, int var2) {
  // If type T != foo || T != bar then show error message.
  static_assert(std::is_same<T, foo>::value || 
    std::is_same<T, bar>::value,
    "This function does not support the specified type.");
  return var1.member + var2;

# Fundamental type traits

There are a number of different type traits that compare more general types.

Is Integral:

Evaluates as true for all integer types int, char, long, unsigned int etc.

std::cout << std::is_integral<int>::value << "\n"; // Prints true.
std::cout << std::is_integral<char>::value << "\n"; // Prints true.
std::cout << std::is_integral<float>::value << "\n"; // Prints false.

Is Floating Point:

Evaluates as true for all floating point types. float,double, long double etc.

std::cout << std::is_floating_point<float>::value << "\n"; // Prints true.
std::cout << std::is_floating_point<double>::value << "\n"; // Prints true.
std::cout << std::is_floating_point<char>::value << "\n"; // Prints false.

Is Enum:

Evaluates as true for all enumerated types, including enum class.

enum fruit {apple, pair, banana};
enum class vegetable {carrot, spinach, leek};
std::cout << std::is_enum<fruit>::value << "\n"; // Prints true.
std::cout << std::is_enum<vegetable>::value << "\n"; // Prints true.
std::cout << std::is_enum<int>::value << "\n"; // Prints false.

Is Pointer:

Evaluates as true for all pointers.

std::cout << std::is_pointer<int *>::value << "\n"; // Prints true.
typedef int* MyPTR;
std::cout << std::is_pointer<MyPTR>::value << "\n"; // Prints true.
std::cout << std::is_pointer<int>::value << "\n"; // Prints false.

Is Class:

Evaluates as true for all classes and struct, with the exception of enum class.

struct FOO {int x, y;};
class BAR {
  int x, y;
enum class fruit {apple, pair, banana};
std::cout << std::is_class<FOO>::value << "\n"; // Prints true.
std::cout << std::is_class<BAR>::value << "\n"; // Prints true.
std::cout << std::is_class<fruit>::value << "\n"; // Prints false.
std::cout << std::is_class<int>::value << "\n"; // Prints false.

# Remarks

Type traits are templated constructs used to compare and test the properties of different types at compile time. They can be used to provide conditional logic at compile time that can limit or extend the functionality of your code in a specific manner. The type traits library was brought in with the c++11 standard which provides a number different functionalities. It is also possible to create your own type trait comparison templates.