Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Tagged as

C++ <type_traits> Header

, 18 May 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
C++ header

For this entry, I would like to introduce the type_traits header file. This file contains utility templates that greatly simplify work when writing template-based libraries. This is especially true for libraries that employ template meta-programming. The header file is available with C++ TR1 and above. This library provides tools to identify types, their qualifying properties and even peel-off properties one-by-one programmatically at compile-time. There are also transformation meta-functions that provide basic meta-programming operations such as a compile-time conditional.

The definitions in type_traits will save us a lot of time implementing Alchemy. As I introduce some of the tools that are available in the header, I will also demonstrate how these operations can be implemented. This will help you understand how to construct variations of the same type of solution when applying it in a different context. As an example of this, I will create a construct that behaves similarly to the tertiary operator, but is evaluated at compile-time.

Types and Values

Creating C++ template meta-programs are essentially functional programs. Functional programs compute with mathematical expressions. You will receive the same result each time you call a function with a specific set of parameters. As expressions are declarative, state and mutable data are generally avoided. Meta-programs are structured in a way to require the compiler to calculate the results of the expressions as part of compilation. The two constructs that we have to work with are integer constants, to hold calculation results, and types, which we can use like function calls.

Define a Value

Meta-program constants are declared and initialized statically. Therefore, these values are limited to integer-types. We cannot use string literals or floating-points because these types of static constants cannot be initialized in place with the class or struct definition. Sometimes, you will see code declared with enumerations. I believe this is to prevent meta-programming objects from using space. It is possible for users to take the address of static constants, and if this occurs, the compiler must allocate space for the constant, even if the object is never instantiated. It is not possible to take the address of an enumeration declaration. Therefore no memory must be allocated when an enumeration is used.

Since I started using type_traits, I don't worry about it so much. I use the integral_constant template to define values. By convention, values are given the name value. This is important in generic programming to allow the object development to remain loosely coupled and independent of the design of other objects. The example below demonstrates how the integral_constant is typically used. Please note that all of these constructs live in the std:: namespace, which I will be omitting in these examples.

// Implement the pow function to calculate
// exponent multiplication in a meta-function.
template < size_t BaseT, size_t ExpT >
struct Pow
  : integral_constant< size_t, 
                       BaseT * Pow< BaseT , ExpT-1>::value >
{ };

// Specialization: Terminator for exp 1.
template < size_t BaseT >
struct Pow< BaseT, 1 >
  : integral_constant< size_t, BaseT >
{ };

// Specialization: Special case for exp 0.
template < size_t BaseT >
struct Pow< BaseT, 0 >
  : integral_constant< size_t, 1 >
{ };

Call (Instantiate) our meta-function:

int x = Result< 3,3 >::value; // 27

integral_constant

All that is required to implement the integral_constant, is a definition of a static constant in the template struct. Structs are generally used because of their default public member access. Here is the most basic implementation:

template < typename T,
           T ValT>
struct integral_constant
{
  static const T value = ValT;
};

Yes it's that simple. The implementation in the C++ Standard Library goes a little bit further for convenience. Just as with the STL Containers, typedefs are created for the value_type and type of the object. There is also a value conversion operator to implicitly convert the object to the value_type. Here is the complete implementation:

template < typename T,
           T ValT>
struct integral_constant
{
  typedef T                           value_type;
  typedef integral_constant< T, ValT> type;

  static const T value = ValT;

  operator value_type() const
  {
    return value;
  }
};

Two typedefs have been created to reduce the amount of code required to perform Boolean logic with meta-programs:

typedef integral_constant< bool, true >  true_type;
typedef integral_constant< bool, false > false_type;

Compile-time Decisions

With meta-programming, there is only one way to define a variable, and there are many ways to create decision making logic. Let's start with one that is very useful for making decisions.


is_same

This template allows you to test if two types are the same type.

template < typename T,
           typename U >
struct is_same
  : false_type
{ };

// Specialization: When types are the same
template < typename T >
struct is_same< T,T >
  : true_type
{ };

The compiler always looks for the best fit. That way, when multiple templates would be suitable, only the best match will be selected, if that is possible. In this case, the best match for when both types are the exact same, is our specialization that indicates true.


conditional

It's time to define a branching construct to enable us to make choices based on type. The conditional template is the moral equivalent of the if-statement for imperative C++.

// The default implementation represents false
template < bool Predicate,
           typename T,
           typename F >
struct conditional
{
  typedef F type;
};

// Specialization: Handles true
template < typename T,
           typename F >
struct conditional < true, T, F >
{
  typedef T type;
};

Applying These Techniques

I have just demonstrated how three of the constructs defined in the type_traits header could be implemented. The techniques used to implement these constructs are used repeatedly to create solutions for evermore complex problems. I would like to demonstrate a construct that I use quite often in my own code, which is both built upon the templates I just discussed, and implemented with the same techniques used to implement those templates.


value_if

While the conditional template will define a type based on the result a Boolean expression, I commonly want to conditionally define a value based on the result of a Boolean expression. Therefore, I implemented the value_if template. This makes use of the integral_constant template and a similar implementation as was used to create the conditional template. This gives me another tool to simplify the complex parametric expressions that I often encounter.

template < bool Predicate,
           typename T,
           T TrueValue,
           T FalseValue >
struct value_if
  : integral_constant< T, FalseValue >
{ };

// Specialization: True Case
template < typename T,
           T TrueValue,
           T FalseValue >
struct value_if < true, T, TrueValue, FalseValue >
  : integral_constant< T, TrueValue >
{ };

Summary

I just introduced you to the type_traits header in C++. If you have not yet discovered this header, you should check it out. It can be very useful, even if you are not creating template meta-programs. Here is a reference link to the header from cppreference.com[^].


With the basic constructs that I introduced in this entry, I should now be able to create more sophisticated ways to interact with the Typelist[^] that I previously discussed for Alchemy. With the simple techniques used above, we should be able to implement template expressions that will query a Typelist type by index, get the size of the type at an index, and similarly calculate the offset in bytes from the beginning of the Typelist. The offset will be one of the most important pieces of information to know for the Alchemy implementation.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Paul M Watt
Architect L3 Communications
United States United States
I have been developing software for almost two decades. The majority of my expertise as well as my strongest language is C++ with the networking software as my domain of focus. I have had the opportunity to develop:
* Desktop applications (Data Layer, Business Layer, Presentation Layer)
* Application virtualization
* Web clients
* Mobile device management software
* Network Device drivers
* Embedded system software for
- IP routers
- ATM switches
- Microwave frequency radio/modems
* Distributed processing w/ parallel algorithms.
 
Over the years I have learned to value maintainable design solutions first. This has allowed me to adapt my projects to meet the challenges that inevitably appear during development, including:
* My own misjudgments
* Incomplete requirements
* Feature creep
* Poor decisions for which I have no control
 
I am a Mentor and frequent contributor to CodeProject.com with tutorial articles that teach others about the inner workings of the Windows APIs.
 
I am the creator of an open source project on GitHub called Network Alchemy[^], which is a set of Network APIs that are focused on helping developers write robust network communication software.
 
I maintain my own repository and blog at CodeOfTheDamned.com/[^], because code maintenance does not have to be a living hell.
 
Then for fun I will tinker with my ray-tracer when ever I upgrade my hardware to see what it is capable of doing.
Follow on   Twitter   LinkedIn

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.150129.1 | Last Updated 18 May 2014
Article Copyright 2014 by Paul M Watt
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid