IBM Z and LinuxONE - Languages - Group home

Introduction to Type Traits in the C++ standard library

  

The origin of Type Traits can be traced back to the TR1 and boost libraries. However, the Type Traits feature is formally introduced into the C++ language in the C++11 standard library. As its name suggests, Type Traits exposes different characteristics of types, or simply the “type of type”. In many C++ programming practices, especially these in template metaprogramming, developers may find it difficult to build a template work for all types without knowing the characteristics of a type. That’s the key reason for the emergence of Type Trait.

In the book “Understanding C++11”, for instance, we used Type Traits to analyze the characteristics of types.
 
#include <type_traits>
template<typename T>
constexpr bool is_pod(T) {
    return std::is_pod<T>::value;
}
 
In the example, function template _is_pod is defined as a wrapper for the static member of template class is_pod. In the standard library of C++11, template class is_pod is defined type_traits. Using the function, we can determine whether the data is of POD type.
 
int main(){
    int a;
    std::cout << is_pod(a) << std::endl;
}

Note that the function is_pod is declared as constexpr, which is another famous feature in C++11 that can evaluate expression at compile time. The return value of is_pod is actually evaluated to true(1). So the main function in the previous example is equivalent to the following code at run time.
 
int main(){
    std::cout << 1 << std::endl;
}


In the proposal of C++11 standard (also known as TR1), language designers added a lot of Type Traits in the <type_traits> header file. For more information, you can see the Primary type categories table at http://en.cppreference.com/w/cpp/types
 

In the original design, the C++ language designers categorized Type Traits in a simple way. And as the language evolves, Type Traits have been constantly modified. You can find all the latest definitions and descriptions of Type Traits from the link above. Apart from determining the traits of types, in <type_traits>, there also exist frequently used class templates such as is_same and enable_if. You can also find more information about the usage of these templates by following the link provided.

As for the implementation, the functionalities for the Type Traits usually rely on the template specifications type by type, which is a common method in template metaprogramming. For instance, the following code can be found in the <type_traits> header file in g++ 4.8.1.
 
  /// is_const
  template<typename>
    struct is_const
    : public false_type { };    // Version 1

  template<typename _Tp>
    struct is_const<_Tp const>
    : public true_type { };    // Version 2
 
Here we specify template is_const to be derived class of false_type and true type. false_type and true_type are two helper classes defined below:
 
  /// integral_constant
  template<typename _Tp, _Tp __v>
    struct integral_constant
    {
      static constexpr _Tp                  value = __v;
      typedef _Tp                           value_type;
      typedef integral_constant<_Tp, __v>   type;
      constexpr operator value_type() { return value; }
    };

  template<typename _Tp, _Tp __v>
    constexpr _Tp integral_constant<_Tp, __v>::value;

  /// The type used as a compile-time boolean with true value.
  typedef integral_constant<bool, true>     true_type;

  /// The type used as a compile-time boolean with false value.
  typedef integral_constant<bool, false>    false_type;
 
We may ignore some details, but regard true_type and false_type as class templates that contain one static class member value. The static member is true for true_type and false for false_type.

Thus, through specification, if we use const as the type parameter for template is_const, the constant value of the static member is true(1). This is because the compiler will choose the specification “Version 2” as it matches a const type better. Conversely, when the template matches better to specification “Version 1”, the value of constant static member would be false(0). See the example below:
 
#include <iostream>
#include <type_traits>

int main(){
    int a;
    const int b = 3;
    std::cout << std::is_const<decltype(a)>::value << std::endl;    // 0
    std::cout << std::is_const<decltype(b)>::value << std::endl;    // 1
}
 
Notice that though template metaprogramming would be one technology to implement Type Trait, it does not mean all Type Traits are based on it. In fact, the designers of C++ language leaves some implementations as intrinsics, which need compiler assistance. For instance, here is the definition of POD in g++4.8.1:
 
  /// is_pod
  // Could use is_standard_layout && is_trivial instead of the builtin.
  template<typename _Tp>
    struct is_pod
    : public integral_constant<bool, __is_pod(_Tp)>
    { };

__is_pod listed above is an instance of compiler intrinsics. In fact, the following Type Trait template classes all need compiler’s assistance:
 
template <class T> struct is_class;
template <class T> struct is_union;
template <class T> struct is_enum;
template <class T> struct is_polymorphic;
template <class T> struct is_empty;
template <class T> struct has_trivial_constructor;
template <class T> struct has_trivial_copy;
template <class T> struct has_trivial_assign;
template <class T> struct has_trivial_destructor;
template <class T> struct has_nothrow_constructor;
template <class T> struct has_nothrow_copy;
template <class T> struct has_nothrow_assign;
template <class T> struct is_pod;
template <class T> struct is_abstract;

So to summarize, template metaprogramming together with the assistance of compilers make the miracles of Type Traits happen. Furthermore, Type Trait makes programming with templates easier. Thus, C++11 adopts the feature into the new standard.

 

Authors: Garfee Guan, Yvonne Ma