C++ Traits explained

Traits in C++ is a concept of using templates in order to implement different behaviour for the same class/function depending on the types that are used inside of them.

Ok, so why am I writing about this again?

There are many readings about traits, but for some reason I had trouble with understanding them, probably because of lack of life examples. I finaly got it when I had to use them on my own. So I decided to tell about my work, so maybe it will help someone to understand what it is and what cool stuff can be done with it.

Goal

The goal of this example is to implement a simple container for one value. We want to keep it elastic, so we use templates:
template<typename T>
class myContainer
{
public:
    myContainer(T element)
    {
        myElement = element;
    }
private:
    T myElement;
};
Quickly we can notice, that this solution will make troubles with dynamically allocated objects:
myContainer<int>(123); // Is OK
myContainer<int*>(new int(123)); // Memory Leak!
The solution would be to delete allocated object in the destructor, but we can do this only for pointer types. How to detect them? This is where traits comes with help.

Deciding which method to use

Let’s look at this class:
template <bool Predicate, typename Result = void>
class TEnableIf;

template <typename Result>
class TEnableIf<true, Result>
{
public:
    typedef Result Type;
};

template <typename Result>
class TEnableIf<false, Result>
{ };
The purpose of this class is to give us a return type every time when the Predicate parameter is set to true. In other words code:
inline typename TEnableIf<true, void>::Type DoSomething()
{}
will be translated into:
inline void DoSomething()
{}
Why do we need this? We need this, because now we can create conditions, which tells the compiler which functions we can use for different variable types.

Let’s create a function, that will have different body for pointers and non-pointers. First of all we need conditions (traits):
template<typename T> struct IsPointer { enum { Value = false }; };
template<typename T> struct IsPointer<T*> { enum { Value = true }; };
And now the functions:
template <typename T>
inline typename TEnableIf<IsPointer<T>::Value, void>::Type DestroyItem(T Element)
{
    // The T is a pointer, we should delete the Element.
    delete Element;
}
 
template <typename T>
inline typename TEnableIf<!IsPointer<T>::Value, void>::Type DestroyItem(T Element)
{
    // The T is a non-pointer, do nothing.
}
See what we did there? The compiler will generate DestroyItem functions for all used by us types, but it will generate different functions for pointer and non-pointer types!
Now the last thing is to use these methods in the destructor of our container:
~myContainer()
{
    DestroyItem<T>(myElement);
}

What next?

We can of course create trait functions that returns the value:
template <typename T>
inline typename TEnableIf<IsPointer<T>::Value, bool>::Type IsPointerType()
{
    return true;
}
 
template <typename T>
inline typename TEnableIf<!IsPointer<T>::Value, bool>::Type IsPointerType()
{
    return false;
}
Traits allows us to do more complex conditions. For example – what if we, for some reasons, don’t want to delete pointers to ints?
// Default value for all types
template<typename T> struct NeverDelete { enum {Value = false}; };
// Never delete type int*
template<> struct NeverDelete<int*> {enum {Value = true}; };
 
// Use delete if T is a pointer and is not an int*
template <typename T>
inline typename TEnableIf<IsPointer<T>::Value && !NeverDelete<T>::Value, void>::Type DestroyItem(T Element)
{
    delete Element;
}
 
// Don't delete if T is not a pointer or if it is int*
template <typename T>
inline typename TEnableIf<!IsPointer<T>::Value || NeverDelete<T>::Value, void>::Type DestroyItem(T Element)
{}
For more clarity we can create one big struct for traits!
// Create one struct with complex traits for future use
template<typename T> struct TTypeTraits
{
    enum { RequiresDelete = IsPointer<T>::Value && !NeverDelete<T>::Value };
    enum { SomeOtherTrait = true };
};
 
// Use delete if T RequiresDelete - is a pointer and is not a int*
template <typename T>
inline typename TEnableIf<TTypeTraits<T>::RequiresDelete, void>::Type DestroyItem(T Element)
{
    delete Element;
}
 
// Use delete if T do not RequiresDelete - is not a pointer or is a int*
template <typename T>
inline typename TEnableIf<!TTypeTraits<T>::RequiresDelete, void>::Type DestroyItem(T Element)
{}
As you can see traits can be nested:
// Checking if the type is integral
template<typename T> struct IsIntegral { enum { Value = false }; };
template<> struct IsIntegral<int> { enum { Value = true }; };
 
// Checking if the type is float
template<typename T> struct IsFloat { enum { Value = false }; };
template<> struct IsFloat<float> { enum { Value = true }; };
 
// Checking if the type is arithmetic (is integral or type)
template<typename T> struct IsArithmetic {enum { Value = IsIntegral<T>::Value || IsFloat<T>::Value }; };
 
// Checking if type is POD (is arithmetic or pointer)
template<typename T> struct IsPODType { enum { Value = IsArithmetic<T>::Value || IsPointer<T>::Value }; };
Except of functions and specific types there are many other traits that can help us with types detection:
// Check if it is a void type
template<typename T> struct IsVoidType { enum { Value = false }; };
template<> struct IsVoidType<void> { enum { Value = true }; };
 
// Check if it is a reference
template<typename T> struct IsReferenceType { enum { Value = false }; };
template<typename T> struct IsReferenceType<T&> { enum { Value = true }; };
 
// Check if two types are the same
template<typename A, typename B> struct IsSame { enum { Value = false }; };
template<typename T> struct IsSame<T, T> { enum { Value = true }; };
 
// Check if it is a function
template <typename T> struct IsFunction { enum { Value = false }; };
template <typename RetType, typename... Params> struct IsFunction<RetType(Params...)> { enum { Value = true }; };
You can also, if there is a need for this, simply check the type inside the if condition:
if (IsPointer<T>::Value == true)
{
    std::cout << "We have a pointer here!" << std::endl;
}

C++11

The C++11 has many traits predefined inside the standard library, also the enable_if class template, so if you use it there is no need for defining many of traits by yourself. In C++11 our methods will look like this:
#include <type_traits>
 
template <typename T>
inline typename std::enable_if<std::is_pointer<T>::value, void>::type DestroyItem(T Element)
{
    delete Element;
}
 
template <typename T>
inline typename std::enable_if<!std::is_pointer<T>::value, void>::type DestroyItem(T Element)
{}
For other traits in C++11 check the Type traits section of this page.

Summary

The power of traits and templates in general is that the code is generated during the compilation process, not during the runtime, which makes the final code run super fast. It also compansate the rather poor RTTI of C++. I hope the whole article clearen up you the concept of traits. Happy coding!