Frank Frank - 11 days ago 8
C++ Question

SFINAE: Compiler doesn't pick the specialized template class

I have an SFINAE problem:

In the following code, I want the C++ compiler to pick the specialized functor and print "special", but it's printing "general" instead.

#include <iostream>
#include <vector>

template<class T, class V = void>
struct Functor {
void operator()() const {
std::cerr << "general" << std::endl;
}
};

template<class T>
struct Functor<T, typename T::Vec> {
void operator()() const {
std::cerr << "special" << std::endl;
}
};

struct Foo {
typedef std::vector<int> Vec;
};

int main() {
Functor<Foo> ac;
ac();
}


How can I fix it so that the specialized struct is used automatically? Note I don't want to directly specialize the
Functor
struct on
Foo
, but I want to specialize it on all types that have a
Vec
type.

P.S.: I am using g++ 4.4.4

Answer

Sorry for misleading you in the last answer, I thought for a moment that it would be simpler. So I will try to provide a complete solution here. The general approach to solve this type of problems is to write a traits helper template and use it together with enable_if (either C++11, boost or manual implementation) to decide a class specialization:

Trait

A simple approach, not necessarily the best, but simple to write would be:

template <typename T>
struct has_nested_Vec {
    typedef char yes;
    typedef char (&no)[2];
    template <typename U>
    static yes test( typename U::Vec* p );
    template <typename U>
    static no test( ... );

    static const bool value = sizeof( test<T>(0) ) == sizeof(yes);
};

The approach is simple, provide two template functions, that return types of different sizes. One of which takes the nested Vec type and the other takes ellipsis. For all those types that have a nested Vec the first overload is a better match (ellipsis is the worst match for any type). For those types that don't have a nested Vec SFINAE will discard that overload and the only option left will be the ellipsis. So now we have a trait to ask whether any type has a nested Vec type.

Enable if

You can use this from any library, or you can roll your own, it is quite simple:

template <bool state, typename T = void>
struct enable_if {};

template <typename T>
struct enable_if<true,T> {
    typedef T type;
};

When the first argument is false, the base template is the only option, and that does not have a nested type, if the condition is true, then enable_if has a nested type that we can use with SFINAE.

Implementation

Now we need to provide the template and the specialization that will use SFINAE for only those types with a nested Vec:

template<class T, class V = void>
struct Functor {
    void operator()() const {
        std::cerr << "general" << std::endl;
    }
};
template<class T>
struct Functor<T, typename enable_if<has_nested_Vec<T>::value>::type > {
    void operator()() const {
        std::cerr << "special" << std::endl;
    }
};

Whenever we instantiate Functor with a type, the compiler will try to use the specialization, which will in turn instantiate has_nested_Vec and obtain a truth value, passed to enable_if. For those types for which the value is false, enable_if does not have a nested type type, so the specialization will be discarded in SFINAE and the base template will be used.

Your particular case

In your particular case, where it seems that you don't really need to specialize the whole type but just the operator, you can mix the three elements into a single one: a Functor that dispatches to one of two internal templated functions based on the presence of Vec, removing the need for enable_if and the traits class:

template <typename T>
class Functor {
   template <typename U>
   void op_impl( typename U::Vec* p ) const {
      std::cout << "specialized";
   }
   template <typename U>
   void op_impl( ... ) const {
      std::cout << "general";
   }
public:
   void operator()() const {
      op_impl<T>(0);
   }
};