考虑以下代码:
template <class...>
using void_t = void;
template <class T>
void bar(T){}
template <class T>
void bar(T, void_t<decltype(std::declval<T>().foo())>* = 0) {}
struct A { void foo(); } a;
bar(a); // gives a compiler error on ambiguous call
那么问题来了,为什么这些重载会模棱两可?为什么编译器不认为第二个重载比第二个重载更严格、更专业?
最佳答案
您正在尝试使用 SFINAE 在特定情况下强制选择特定候选人(此处,可调用实体 foo
的存在在 T
中不带任何参数> 左值)。这里到底发生了什么?
带有 void_t
的模板是为您的 struct A
定义的,因此在调用时您有两个有效的重载决议候选者。如果你想使用 SFINAE,你必须确保对于任何给定的调用只有一个重载可用。为此,您应该首先将测试嵌入到类型特征中。为此,您可以在 Yakk's can_apply
facility 上举个例子我无耻地复制在这里,因为它非常符合您的需要:
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>;
现在我们只需要在我们的模板定义中使用上面定义的特征:
// 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;
}
您可以在 Coliru 上看到一个运行示例.
关于具有默认参数的函数的 C++ 偏序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39489405/