SFINAE not working on llvm/clang

2019-09-01 06:27发布

问题:

In the following code I'm trying to call a functor with whatever it takes as its parameters, "whatever" being a limited set of options (the two here are not the only ones in my code).

#include <memory>
#include <iostream>

template<class T>
struct call_with_pointer {
    // last resort: T*
    template<class Callable>
    static auto call(Callable &callable, const std::shared_ptr<T> &param) -> decltype(callable(param.get())) {
        return callable(param.get());
    }
};

template<class T>
struct call_with_shared : public call_with_pointer<T> {

    // best: call with shared_ptr<T>.
    // SFINA
    // error: Candidate template ignored: substitution failure [with Callable = Test]: no matching function for call to object of type 'Test'
    template<class Callable>
    static auto call(Callable &callable, const std::shared_ptr<T> &param) -> decltype(callable(param)) {
        return callable(param);
    }

    using call_with_pointer<T>::call;
};


class Test {
public:
    bool operator () (int * x) {
        return *x == 42;
    }
};

int main ()
{
    Test t;

    auto i = std::make_shared<int>(4);

    auto x = call_with_shared<int>::call(t, i); // No matching function for call to 'call'

    return 0;
}

This code works just fine in VS and GCC. Unfortunately it does not in clang. The error message is:

No matching function for call to 'call'

Candidate template ignored: substitution failure [with Callable = Test]: no matching function for call to object of type 'Test'

So it ignores the candidate that uses the smart pointer. Good. But it does not seem to continue with considering the inherited call that would work just fine.

Question: How can I work around this? How can I make llvm do the right thing here?

回答1:

Try the following:

template<class T>
struct call_with_pointer {
    // last resort: T*
    template<class Callable>
    static auto call(Callable &callable, const std::shared_ptr<T> &param) -> decltype(callable(param.get())) {
        return callable(param.get());
    }
};

template<class T>
struct call_with_pointer_2 {
    // last resort: T*
    template<class Callable>
    static auto call(Callable &callable, const std::shared_ptr<T> &param) -> decltype(callable(param)) {
        return callable(param);
    }
};

template<class T>
struct call_with_shared : public call_with_pointer<T>, public call_with_pointer_2<T>{
    using call_with_pointer<T>::call;
    using call_with_pointer_2<T>::call;
};


回答2:

Strictly speaking, clang is right because of 7.3.3p15 of C++11(the using declaration of the inherited function template is ignored because it has the same name and parameters as a member function template of the derived class). Although it is pretty clear that that paragraph is defective in not considering these nonconflicting declarations.

You can work it around by using something like typename YieldFirstType<std::shared_ptr<T>, Callable>::type as the second parameter type in one of your templates.