2.5.13 Type Traits

What was the problem

Let’s have a look at this small function:

template<typename T> 
T mix(T a, T b, T r) 
{ 
  return r * a + (1 - r) * b; 
}

This is a quite basic function that just combine two values with a weighted sum. Its implementation tells us that r should probably be in [0,1], but most importantly, the computation makes sense only if T is a float-like type. If we want to prevent the compiler or the programmer to call this function with an incompatible type, we can add some SFINAE15, for example by introducing a type deduced from T that would not be defined if T is not a float-like type. In C++98 the implementation could have been similar to:

// This struct declares a type identical to T if and only if T is 
// a float-like type. We are going to specialize it only for the 
// valid types. For the other types, the missing type declaration 
// in the struct will trigger a substitution failure. 
template<typename T> 
struct only_if_float_like; 
 
template<> 
struct only_if_float_like<float> 
{ 
  typedef float type; 
}; 
 
template<> 
struct only_if_float_like<double> 
{ 
  typedef double type; 
}; 
 
template<typename T>
typename only_if_float_like<T>::type mix(T a, T b, T r)
{ 
  return r * a + (1 - r) * b; 
}

Aside from the fact that long double is not handled, does it work as expected?

void test() 
{ 
  // float and double are handled as expected. 
  printf("%f\n", mix(1.f, 3.f, 0.5f)); 
  printf("%f\n", mix(1.d, 3.d, 0.5d)); 
 
  // Using integers triggers an error, we can say that it fails successfully! 
  // printf("%d\n", mix(1, 3, 2)); 
}

Typing custom type like only_if_float_like for every use case is repetitive, so we would certainly end up splitting it into two types, that could be used like only_if<is_float_like<T>::value, T>::type.

How the Problem is Solved

PIC <type_traits>

Lucky us, C++11 greatly simplify this work by introducing the required types, in the form of std::enable_if and std::is_floating_point_type. Using these types the whole implementation is reduced to the following:

#include <type_traits> 
 
template<typename T>
typename std::enable_if<std::is_floating_point<T>::value, T>::type
mix(T a, T b, T r) 
{ 
  return r * a + (1 - r) * b; 
}

There are many other predicates and operations added in <type_traits> in C++11, that can greatly help for metaprogramming. I can only suggest to have a look at them on a nice online reference.

15Substitution Failure Is Not An Error, is a principle in template instantiations according to which the compiler must not emit an error if it fails to instantiate a template. Instead it should try another template candidate. This feature is often used and abused to provide multiple implementation behind the same function signature.