我试图留下struct A
一个函数foo
(打印0),如果它的参数有模板方法isA<void>
而另一个(打印1)如果没有。这段代码(简化为下面的最小例子)编译(尝试使用gcc 6.1.0和clang-3.9.0以及显式--std=c++14
选项)并运行。
但它打印1,但我确信它会打印0.我想知道我错在哪里,但真正的问题是:如何使这项工作正确?
请仅使用C ++ 14解决方案。
#include <type_traits>
#include <iostream>
#include <utility>
using std::enable_if;
using std::declval;
using std::true_type;
using std::false_type;
using std::cout;
template<int M>
struct ObjectX
{
template<typename C>
bool isA() { return false; }
};
struct XX : ObjectX<23456> {
int af;
};
template <typename ObjType> using has_dep = decltype(declval<ObjType>().template isA<void>());
template <typename, typename = void>
struct has_isa : public false_type {};
template <typename ObjType>
struct has_isa<ObjType, has_dep<ObjType> > : public true_type {};
template<typename ObjType>
struct A
{
template<typename T = void>
typename enable_if<has_isa<ObjType>::value, T>::type
foo() {
cout << "called foo #0" << "\n";
}
template<typename T = void>
typename enable_if<!has_isa<ObjType>::value, T>::type
foo() {
cout << "called foo #1" << "\n";
}
};
int
main()
{
A<XX> axx;
// XX().template isA<void>(); -- to check, that we can call it and it exists
axx.foo();
return 0;
}
答案 0 :(得分:4)
此计划存在两个问题。
首先,has_dep<XX>
是bool
。当我们尝试has_dep<XX>
时,添加默认模板参数意味着这实际上是has_dep<XX, void>
。但专业化是has_dep<XX, bool>
- 这与我们实际查找的内容不符。 bool
与void
不符。这就是has_dep<XX>
为false_type
的原因。对此的解决方案是std::void_t
,我建议阅读该Q / A以了解其工作原理。在您的专业化中,您需要使用void_t<has_dep<ObjType>>
代替。
其次,这是不对的:
template<typename T = void>
typename enable_if<has_isa<ObjType>::value, T>::type
SFINAE仅发生在替换的直接上下文中,并且类模板参数不在函数模板替换的直接上下文中。这里正确的模式是:
template <typename T = ObjType> // default to class template parameter
enable_if_t<has_isa<T>> // use the function template parameter to SFINAE
foo() { ... }
进行这两个修复,程序按预期工作。
答案 1 :(得分:2)
你的sfinae失败了,因为has_isa
选择了错误的专业化。
has_isa<T>
的使用必须是默认实现或专用版本。
如您所定义,您有一个默认参数void:
// default argument ---------v
template <typename, typename = void>
struct has_isa : public false_type {};
然后在表达式has_isa<T>
中,第二个参数必须为void。它与撰写has_isa<T, void>
大致相同。
问题在于:
template <typename ObjType>
struct has_isa<ObjType, has_dep<ObjType>> : public true_type {};
// ^--- what's that type?
即使模板部分排序会考虑这个&#34;重载&#34;更专业,它不会被选中。查看has_dep
:
struct XX {
template<typename C> bool isA() { return false; }
};
template <typename ObjType>
using has_dep = decltype(declval<ObjType>().template isA<void>());
嘿,该类型has_dep<T>
是t.isA<void>()
的返回类型,即bool
!
所以专业版看起来像这样:
template <typename ObjType>
struct has_isa<ObjType, has_dep<ObjType>> : public true_type {};
// ^--- really, this is bool in our case
因此,为了使其正常工作,您必须致电has_isa<T, bool>
。由于这是不切实际的,您应该将您的专业化定义为:
template <typename ObjType>
struct has_isa<ObjType, void_t<has_dep<ObjType>>> : public true_type {};
void_t
的定义如下:
template<typename...>
using void_t = void; // beware for msvc
因此,has_isa<T>
将始终考虑专业化,因为我们发送void
作为第二个模板参数,现在我们的专业化始终以void
作为第二个参数。
另外,正如Barry所说,你的功能没有正确形成,因为sfinae只出现在紧急情况下。你应该这样写:
template<typename T = ObjType>
typename enable_if<has_isa<T>::value, void>::type
foo() { // ^--- sfinae happens with T
cout << "called foo #0" << "\n";
}
如果您不希望公开模板参数,只需将该功能设为私有:
template<typename ObjType>
struct A {
public:
void foo() {
foo_impl();
}
private:
template<typename T = ObjType>
typename enable_if<has_isa<T>::value, void>::type
foo_impl() {
cout << "called foo #0" << "\n";
}
template<typename T = ObjType>
typename enable_if<!has_isa<T>::value, void>::type
foo_impl() {
cout << "called foo #1" << "\n";
}
};
答案 2 :(得分:1)
你的问题是你专攻错误的课程:
您应该强制has_dep
返回void
。
template <typename ObjType> using has_dep = decltype(static_cast<void>(declval<ObjType>().template isA<void>()));
所以这里
template <typename ObjType>
struct has_isa<ObjType, has_dep<ObjType> > : public true_type {};
// It is really <bjType, void> you specialize.