Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

The Type Traits Library, Part I

Last updated Jan 1, 2003.

The type trait library, which is included in the recently approved TR1, defines a uniform and portable interface for querying, comparing and modifying type traits. In the first part of this series I explain what type traits are, why they are needed and how they are used.

The Motivation for Type Traits

Theoretically, a generic algorithm should be type agnostic. When you sort a sequence of int or double, the compiler uses the same template mold to synthesize the specializations sort<int> and sort<double>, respectively. However, in certain cases the algorithm needs more information about the types of the objects it operates on. These pieces of information, collectively referred to as type traits, are necessary for ensuring the correctness of the algorithm. In other cases, type traits enable the implementor to optimize the algorithm for certain types. A classic example is a copy algorithm that is optimized for POD types. If the collection being copied contains POD objects, the algorithm can use memcpy() instead of copying individual objects by invoking their copy constructor. Similarly, every algorithm defines the categories of its iterators. Thus, while distance() takes two forward iterators as arguments, nth_element takes random access iterators. Compilers can use iterator traits to ensure that the correct iterator category is used.

Type Traits in the Real World

Generic libraries use traits extensively. However, even in user-written code type traits are sometimes necessary. Consider a template function that explicitly invokes the destructors of objects constructed by placement new. If the objects have a trivial destructor, then the destructor invocation can be safely elided. However, to perform this optimization, the template function needs to know whether the objects’ class has a trivial destructor.

The Type Traits Library Design

Type traits are evaluated at compile-time, as are all meta-functions. This property is a major advantage since type traits do not incur runtime overhead. The problem is that unlike ordinary functions that are executed at runtime, type traits have to use artful techniques to compute and report the results at compile time. This constraint led to the design Boost’s type traits library. This library served as the basis for TR1 type traits library.

Type traits, e.g., is_pod, is_pointer, has_virtual_destructor etc. are implemented as distinct classes (each class represents a specific type trait). The trait classes share a unified design, whereby each class inherits from the type true_type if the type being queried has the specified property. Otherwise, the trait class inherits from false_type. The use of base classes to mark the existence/absence of a type trait may seem odd if you haven’t experienced with metaprogramming before. However, you will shortly see that the type traits library has a simple and consistent interface.

Remember: since all type traits classes are derived from true_type or false_type, these two types can be used in generic programming to determine the properties of a given type and introduce optimizations that are appropriate for that case. For instance, to determine whether the template parameter T is void, you use the is_void type trait which is defined like this:

template <typename T> 

struct is_void : public false_type{};
template <> 
struct is_void<void> : public true_type{};

The primary template represents the default case. It inherits from false_type, meaning: T isn’t void. For example, if T is int or std::string, the primary template will be selected. If however T is void, the specialization which inherits from true_type will be selected instead.

In addition to querying types, the type-traits library also contains a set of classes that perform a specific transformation on a type. For example, they can remove a top-level const or volatile qualifier from a type. Each class that performs a transformation defines a single typedef-member type that is the result of the transformation. We will get to this later, but for the time being suffice it to say that the type traits library enables you to modify the type traits of a certain type, not just query its properties.

Let’s look at a few expressions based on is_pod to observe the interface of trait classes. We already know that is_pod<T> inherits from true_type if T is a POD type; otherwise, is_pod<T> inherits from false_type. For example, is_pod<int> inherits from true_type, whereas is_pod<std::string> inherits from false_type.

type_trait<T>::type

To obtain the base class types true_type and false_type, use the expression type_trait<T>::type. For example

is_pod<char*>::type is the type true_type.

is_pod<std::istream>::type is the type false_type.

type_trait<T>::value

Sometimes you need an integral expression that evaluates as true or false, instead of using types. The type_trait<T>::value returns an such integral expression. For example, is_pod<int (*)(long)>::value is an integral constant expression that evaluates to true, whereas is_pod<std::vector<int>>::value is an integral constant expression that evaluates to false.

type_trait<T>::value_type

To obtain the type of the expressions returned by the expression is_pod<T>::value, use is_pod<T>::value_type:

is_pod<int>::value_type is the type bool.

is_pod<myclass>::value_type is the type bool.

In addition to querying types, the type-traits library also contains a set of classes that perform a specific transformation on a type. For example, they can remove a top-level const or volatile qualifier from a type. In the next part of this series we will look at a concrete example of using type traits to optimize an algorithm and see how to perform type transformation.