SFINAE: the compiler does not select a specialized template class

I have a SFINAE problem:

In the following code, I want the C ++ compiler to select a specialized functor and print "special", but print "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 this so that the specialized structure is used automatically? Note. I don’t want to directly specialize the Functor structure in Foo , but I want to specialize it in all types of type Vec .

PS: I am using g ++ 4.4.4

+8
c ++ templates sfinae
source share
3 answers

Sorry for the misleading in the last answer, I thought it would be easier. Therefore, I will try to provide a complete solution here. A general approach to solving this type of problem is to write a helper template for the traits and use it together with enable_if (either C ++ 11, boost, or a manual implementation) to solve the specialization of the class:

Tre

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

 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 them accepts a nested Vec type, and the other accepts ellipsis. For all those types that have a nested Vec , the first overload is the best match (ellipsis is the worst match for any type). For those types that do not have nested Vec SFINAE, they will discard this overload, and the ellipsis will be the only option left. So now we have a trait to ask if any type has a nested Vec type.

Enable if

You can use it from any library, or you can roll on your own, it's 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 does not have a nested type , if the condition is true , then enable_if has a nested type , which we can use with SFINAE.

Implementation

Now we need to provide a template and specialization that SFINAE will use only for those types with embedded 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 create an instance of Functor with a type, the compiler will try to use the specialization, which in turn will create an instance of has_nested_Vec and get the true value passed to enable_if . For those types for which false , enable_if does not have a nested type type , so specialization will be discarded in SFINAE and the base template will be used.

Your special case

In your specific case, when it seems to you that you don’t need to specialize the whole type, but just an operator, you can combine three elements into one: a Functor , which sends one of two internal template functions based on Vec , eliminating the need for enable_if and feature 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); } }; 
+11
source share

Although this is an old question, I think it is still worth providing some more alternatives for quickly fixing the source code.

The main problem is not using SFINAE (this part is really beautiful), but with matching the default parameter in the main template ( void ) with the argument provided in the partial specialization ( typename T::Vec ). Due to the default setting in the main template, Functor<Foo> actually means Functor<Foo, void> . When the compiler tries to create an instance using the specialization, it tries to match two arguments with those specified in the specialization, and does not work, since void cannot be replaced with std::vector<int> . It then returns to the instance using the primary template.

So the fastest fix, assuming all your Vec are equal to std::vector<int> s, is to replace the string

 template<class T, class V = void> 

with this

 template<class T, class E = std::vector<int>> 

Specialization will now be used because the arguments will match. Simple but too restrictive. Clearly, we need to better control the type of argument in the specialization so that it matches what we can specify as the default parameter in the main template. One quick solution that does not require defining new features is the following:

 #include <iostream> #include <vector> #include <type_traits> template<class T, class E = std::true_type> struct Functor { void operator()() const { std::cerr << "general" << std::endl; } }; template<class T> struct Functor<T, typename std::is_reference<typename T::Vec&>::type> { void operator()() const { std::cerr << "special" << std::endl; } }; struct Foo { typedef std::vector<int> Vec; }; int main() { Functor<Foo> ac; ac(); } 

This will work for any type of Vec that might make sense here, including basic types and arrays, for example, and references or pointers to them.

+2
source share

Another alternative to detect the existence of an element type is to use void_t . Since true partial specializations are preferable to a general implementation, if they correspond to the default parameters (s), we need a type that evaluates to void when it is valid, and is valid only if the specified member exists; this type is usually (and, like C ++ 17, canonically) known as void_t .

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

If your compiler does not support it properly (in early C ++ 14 compilers, unused parameters in alias templates were not guaranteed to guarantee SFINAE by violating void_t above), there is a workaround.

 template<typename... Ts> struct make_void { typedef void type; }; template<typename... Ts> using void_t = typename make_void<Ts...>::type; 

Starting with C ++ 17, void_t is available in the utility library, in type_traits .

 #include <iostream> #include <vector> #include <type_traits> // For void_t. template<class T, class V = void> struct Functor { void operator()() const { std::cerr << "general" << std::endl; } }; // Use void_t here. template<class T> struct Functor<T, std::void_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(); } 

At the same time, the output is special , as expected.


In this case, since we are checking for the existence of an element type, the process is very simple; this can be done without the SFINAE expression or the type_traits library, which allows us to rewrite the test for using C ++ 03 tools, if necessary.

 // void_t: // Place above Functor definition. template<typename T> struct void_t { typedef void type; }; // ... template<class T> struct Functor<T, typename void_t<typename T::Vec>::type> { void operator()() const { std::cerr << "special" << std::endl; } }; 

As far as I know, this should work on most, if not all, compatible with SFINAE C ++ 03-, C ++ 11-, C ++ 14- or C ++ 1z-compatible compilers. This can be useful when working with compilers that are slightly behind the standard or when compiling for platforms that do not yet have C ++ 11 compilers.


For more information about void_t see cppreference .

+1
source share

All Articles