void for traits

One of the great things about high level languages, be it Java or Python, is reflection and the ability to get information about objects and their type at runtime. When you start to think about similar concepts in C++, you'll quickly find out that it's tricky: I guess for performance reasons, the options for reflection are severely limited in C++. One choice is template based reflection using SFINAE techniques (substitution failure is not an error). The concept can be summarized as: when looking up names the C++ compiler will ignore all templates which would result in an invalid type.

For my dependency injection library (dicc) I wanted to check for the existence of a less operator. My initial attempt followed the the SFINAE wikipedia article closely, which resulted in rather long and hard to read code. Luckily, I attended the C++ user group meeting in January, where Stephen Kelly made me aware of a much simpler technique introduced by Walter Brown. Following his ideas, I came up with a much shorter and saner way to check for the existence of the less operator:

namespace void_trait {

template<typename...> 
using void_t = void;

template<typename, typename U = void, typename = void>
struct has_less : std::false_type{
    typedef U type_if_false;
};

template<typename T, typename U>
struct has_less<T, U, 
    void_t<decltype(std::declval<T>() < std::declval<T>())>> 
: std::true_type {
    typedef U type_if_true;
};

}

To understand the basic idea, you need to be aware of the following details:

  • The template definition makes has_less_operator<T, U> equivalent to has_less_operator<T, U, void> regardless of which version is chosen.
  • The template specialization is just an alias for has_less_operator<T, U, void>.
  • However, to be considered, all types in the template specialization have to be valid for type T, which is only the case if the expression inside decltype is valid.
  • Since the template specializations are always preferred, the second variant is selected if T has a less operator, else the first variant.

As an example consider a less function that uses the native less operator if available and else throws an exception. A possible implementation using the has_less trait, again using SFINAE, could look like

using void_trait::has_less;

template<typename T>
typename has_less<T, bool>::type_if_true less(const T& a, const T& b) {
    return a < b;
}

template<typename T>
typename has_less<T, bool>::type_if_false less(const T& a, const T& b) {
    throw std::runtime_error("type has no less operator");
}