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
Even though this is an old question, I think it's still worth providing a couple more alternatives for quickly fixing the original code.
Basically, the problem is not with the use of SFINAE (that part is fine, actually), but with the matching of the default parameter in the primary template (
void
) to the argument supplied in the partial specialization(typename T::Vec
). Because of the default parameter in the primary template,Functor<Foo>
actually meansFunctor<Foo, void>
. When the compiler tries to instantiate that using the specialization, it tries to match the two arguments with the ones in the specialization and fails, asvoid
cannot be substituted forstd::vector<int>
. It then falls back to instantiating using the primary template.So, the quickest fix, which assumes all your
Vec
s arestd::vector<int>
s, is to replace the linewith this
The specialization will now be used, because the arguments will match. Simple, but too limiting. Clearly, we need to better control the type of the argument in the specialization, in order to make it match something that we can specify as the default parameter in the primary template. One quick solution that doesn't require defining new traits is this:
This will work for any
Vec
type that could make sense here, including fundamental types and arrays, for example, and references or pointers to them.Another alternative for detecting the existence of a member type is to use
void_t
. As valid partial specialisations are preferable to the general implementation as long as they match the default parameter(s), we want a type that evaluates tovoid
when valid, and is only valid when the specified member exists; this type is commonly (and, as of C++17, canonically) known asvoid_t
.If your compiler doesn't properly support it (in early C++14 compilers, unused parameters in alias templates weren't guaranteed to ensure SFINAE, breaking the above
void_t
), a workaround is available.As of C++17,
void_t
is available in the utilities library, intype_traits
.With this, the output is
special
, as intended.In this case, since we're checking for the existence of a member type, the process is very simple; it can be done without expression SFINAE or the
type_traits
library, allowing us to rewrite the check to use C++03 facilities if necessary.To my knowledge, this should work on most, if not all, SFINAE-capable C++03-, C++11-, C++14-, or C++1z-compliant compilers. This can be useful when dealing with compilers that lag behind the standard a bit, or when compiling for platforms that don't have C++11-compatible compilers yet.
For more information on
void_t
, see cppreference.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:
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 nestedVec
the first overload is a better match (ellipsis is the worst match for any type). For those types that don't have a nestedVec
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 nestedVec
type.Enable if
You can use this from any library, or you can roll your own, it is quite simple:
When the first argument is
false
, the base template is the only option, and that does not have a nestedtype
, if the condition istrue
, thenenable_if
has a nestedtype
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
:Whenever we instantiate
Functor
with a type, the compiler will try to use the specialization, which will in turn instantiatehas_nested_Vec
and obtain a truth value, passed toenable_if
. For those types for which the value isfalse
,enable_if
does not have a nestedtype
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 ofVec
, removing the need forenable_if
and the traits class: