You are trying to use SFINAE to enforce the selection of a specific candidate in a specific case (here, the presence of a callable entity foo
taking no argument inside a T
lvalue). What does really happen here?
The template with the void_t
is well defined for your struct A
so at the moment of the call you have two valid candidates for the overload resolution. If you want to use SFINAE you have to make sure that for any given call only one overload is available. To do so you should first embed your test in a type trait. To this end you can take example on Yakk's can_apply
facility which I shamelessly copy here since it fits very well with your need:
namespace details {
// if Z<Ts...> is invalid, false_type:
template <template<class...> class Z, class always_void, class... Ts>
struct can_apply : std::false_type {};
// if Z<Ts...> is valid, true_type:
template <template<class...> class Z, class... Ts>
struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...> : std::true_type {};
}
// alias to inject the void type where we need it for SFINAE:
template <template<class...> class Z, class... Ts>
using can_apply = details::can_apply<Z, void, Ts...>;
template <typename T>
using has_foo_t = decltype(std::declval<T>().foo());
template <typename T>
using has_foo = can_apply<has_foo_t, T>;
Now we only have to use the trait defined above in our template definitions:
// The enable_if with the negation is needed to invalidate
// this implementation when T indeed has foo().
// This is what you were missing in your original idea.
template <typename T>
std::enable_if_t<!has_foo<T>::value> bar(T) {
std::cout << "T has no foo(void)" << std::endl;
}
template <typename T>
std::enable_if_t<has_foo<T>::value> bar(T) {
std::cout << "T has a foo(void)" << std::endl;
}
You can see a running example on Coliru.