Reputation: 96845
I'm running into an issue enabling/disabling overloads based on the traits of a parameter pack. This is for another question I am trying to answer. I have a static go()
function that should be called if all the Mixins
types have a static check()
method. Likewise, there should be another static go()
method that should be called if the Mixins
packs doesn't have a check()
method. I attempt to create overloads that enable based on this policy, but for some reason when I call it it chooses the second overload which attempts to call handle()
:
template<class T, class...>
using first = T;
template<class... Mixins>
struct Checker
{
public:
template<class =
first<void,
typename std::enable_if<has_check<Mixins>::value>::type...>>
static void go()
{
auto x{ (Mixins::check(), 0)... };
(void)x;
}
static void go()
{
auto x{ (Mixins::handle(), 0)... };
(void)x;
}
};
struct A { static void check() { std::cout << "check() "; } };
struct B { static void check() { std::cout << "check() "; } };
int main()
{
Checker<A, B>::go();
}
I get an error saying A
has no member named handle()
. This has been bugging me but I can't find a way around it. How do I fix this behavior? How do I correctly coordinate overload resolution based on a parameter pack?
Upvotes: 1
Views: 129
Reputation: 42594
You have nothing to distinguish the two flavors of go
. Overload resolution will prefer the non-template go
when both are viable. The ...
vs. int
technique works:
template<class... Mixins>
struct Checker
{
private:
template<class =
first<void,
typename std::enable_if<has_check<Mixins>::value>::type...>>
static void foo(int)
{
auto x{ (Mixins::check(), 0)... };
(void)x;
}
static void foo(...)
{
auto x{ (Mixins::handle(), 0)... };
(void)x;
}
public:
static void go() {
foo(0);
}
};
Since both overloads of foo
are viable for the call foo(0)
, but foo(int)
is a better match than foo(...)
so that overload resolution will prefer it when it is not SFINAEed out.
EDIT: I made a mistake here, I assumed that
template<class =
first<void,
typename std::enable_if<has_check<Mixins>::value>::type...>>
would correctly SFINAE away the overload of foo
. However, the error here isn't a substitution failure, since the default for the unnamed function template parameter doesn't depend on parameters of the function template. Typically, when you have a class parameterized on a type T
and you want to constrain member function f
based on some property of T
, you force that dependency with an alias for T
:
template <class T>
struct foo {
template <class U=T,
class=typename std::enable_if<std::is_integral<U>::value>::type>
void f();
};
The same trick won't work in the OP's case, however, since the OP's class is parameterized on a pack and there is no way to provide a default for a parameter pack. We have to be trickier:
template<class T = void,
class = first<T, typename std::enable_if<has_check<Mixins>::value, T>::type...>>
static void foo(int)
{
auto x{ (Mixins::check(), 0)... };
(void)x;
}
forcing both first<...>
and the enable_if<...>
expansion to be valid for SFINAE. See it at Coliru.
Upvotes: 1