Curious Curious - 1 month ago 33
C++ Question

SFINAE with functions and emulating partial template specialization

I am trying to make a function X that specializes when a member function Y is provided and if the member function Y is not provided the function X uses the global non member function Y to achieve the same effect.

I am currently trying to achieve this with the following code

template <typename Container, typename std::enable_if_t<std::is_same<
decltype(std::declval<Container>().y()),
decltype(std::declval<Container>().y())>::value>* = nullptr>
void do_something(Container&& container) {
return std::forward<Container>().y();
}

template <typename Container, typename std::enable_if_t<!std::is_same<
decltype(std::declval<Container>().y()),
decltype(std::declval<Container>().y())>::value>* = nullptr>
void do_something(Container&& container) {
return y(std::forward<Container>(container);
}


But in the case when the container has both a member function y and the global non member function y also works on it. There is a compile error because for the second version of the function the template argument is ill formed because a member function y is needed.

Any idea how I can go about resolving this issue?

NOTE: This is different from just detecting whether a class has a function of a given signature. Sorry if the question seems to ask that! The accepted answer below should make my intention clearer.

Answer

The typical approach is to dispatch to a pair of functions where one is preferred if your condition is met and the other is simply a fallback. The nice thing is you don't even need enable_if, just the trailing decltype:

template <class C>
auto do_something_impl(C&& c, int)
    -> decltype(std::forward<C>(c).y())
{
    return std::forward<C>(c).y();
}

template <class C>
auto do_something_impl(C&& c, ...)
    -> decltype(y(std::forward<C>(c))
{
    return y(std::forward<C>(c));
}

And now just pass in 0:

template <class C>
auto do_something(C&& c)
    -> decltype(do_something(std::forward<C>(c), 0))
{
    return do_something_impl(std::forward<C>(c), 0);
}

The conversion sequence to int is better than the one to ..., so if the type has the member function you want, that overload will be preferred even if both are viable candidates.