Question

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();
}

Here is a demo.

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?

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top