5 February 2012

Common behaviour for a hierarchy of classes: the SFINAE technique


This article is devoted to another solution of the problem mentioned in the previous post with a similar title. The main purpose is to have a function which executes one code for objects of some ‘Base’ class and also of any other class derived from ‘Base’, and another flow for objects of all the other classes. This time the solution is implemented using the so called ‘SFINAE’ technique.

‘SFINAE’ stands for ‘Substitution Failure Is Not An Error’ and refers to the situation when the template parameter’s substitution brings to an invalid code, but the compiler does not complain, it just ignores the specialization, more on this just a moment later. The SFINAE programming techniques were first introduced by David Vandevoorde.

To give an example let us refer to ‘enable_if’ structure from boost library. The code is as follows:

template <bool B, class T = void>
struct enable_if {
  typedef T type;
};

template <class T>
struct enable_if<false, T> {};

As you can see, there is not anything unclear here. For false value the template is explicitly specialized not to contain any typedef. For true the structure has a member typedef, namely enable_if::type, which is void by default. So now any piece of code instantiating the enable_if structure and using its type member will be invalid, if the value of template parameter B evaluates to false. If you are still hazy about the details, just wait a little more.

Now we need a compile-time predicate to pass to the enable_if as the first template argument. What we actually want to know is whether the type T is ‘Base’ or derived from ‘Base’. We can use the checker function from the previous article, but I prefer to write an updated version of this checker:

template <typename T>
struct is_descendant
{
       struct true_type { char t[2]; };
       typedef char false_type;
       template <typename U>
       static true_type check(const Base&);
       template <typename U>
       static false_type check(...);

       static const bool value = (sizeof(check<T>(T())) == sizeof(true_type));
       // the same result can be achieved with the following line
       // enum { value = (sizeof(check<T>(T())) == sizeof(true_type)) };
};


The result of our check should be a compile-time constant, otherwise we won’t be able to pass it as a value of another template parameter. Again the sizes of return types are compared to ensure that type T passes the check. Now we have a static predicate which we can pass to enable_if. So let’s move to the final step.

As our support point is class ‘Base’,  we should have a version of function for objects of type ‘Base’:

void func(const Base&)
{
       // call for objects of Base or derived from Base types
}

How do we achieve that this very function is called for all the descendants either? I’ll tell you in a minute. In any case, we need a template function func, too, to be able to call it for any type of object.

template <typename T>
void func(const T& obj)
{
       // call this one for objects of all other classes
}

But this template will be specialized for any type derived from ‘Base’. Now we can take advantage of enable_if and is_descendant. To catch the meaning of what we are trying to do, we can use the names of the structures as descriptors, so we have to enable the template func for all types that are not descendants of ‘Base’. So we have to update the template to something like this one:

template <typename T>
void func(const T& obj,
          typename enable_if<!is_descendant<T>::value >::type* = 0)
{
       // call this one for objects of all other classes
}

Now everything will work as we expect. Namely,
  • if type T is not ‘Base’ neither derived from Base, then is_descendant<T>::value is being evaluated as false, and the negation of it as true, and so the typedef type exists in enable_if, and the code is valid (by default the second argument of func is ‘void*’). Thus the template version is called in this case. 
  • If type T is ‘Base’ or derived from ‘Base’, then is_descendant<T>::value is being evaluated as true, and the negation of it as false, and there is not any typedef in enable_if, including type, so the code of the specialization for type T is invalid, but the compiler does not give an error and just ignores the specialization. The call to func is still valid, as the non-template version of func is an exact (T is ‘Base’) or a good (T is derived from ‘Base’) match.

I want to thank Vasily Milanich for making me aware of such techniques and features as SFINAE and boost’s ‘enable_if’ library. If not his comments and suggestions on my previous article I wouldn’t take time and write this one. Thank you.

1 comment:

  1. 1xbet - No 1xbet Casino | Live dealer casino online
    1xbet is casinosites.one a reliable https://sol.edu.kg/ casino site that offers a great casino games from the best software providers 1xbet 먹튀 for the regulated nba매니아 gambling markets. Rating: 8/10 · ‎Review kadangpintar by a Tripadvisor user · ‎Free · ‎Sports

    ReplyDelete