为什么 SFINAE 在这种情况下对我来说工作不正确以及如何解决它?
Why SFINAE works incorrect for me in this case and how to fix it?
我试图在 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;
}
你的 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?
尽管模板部分排序会认为这个 "overload" 更专业,但它不会被选中。看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>()
的 return 类型,即 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";
}
};
你的问题是你专攻错了class:
你应该强制 has_dep
到 return 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.
这个程序有两个问题。
首先,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 只发生在替换的直接上下文中,class 模板参数不在函数模板替换的直接上下文中。这里的正确模式是:
template <typename T = ObjType> // default to class template parameter
enable_if_t<has_isa<T>> // use the function template parameter to SFINAE
foo() { ... }
修复这两个问题,程序就会按预期运行。
我试图在 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;
}
你的 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?
尽管模板部分排序会认为这个 "overload" 更专业,但它不会被选中。看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>()
的 return 类型,即 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";
}
};
你的问题是你专攻错了class:
你应该强制 has_dep
到 return 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.
这个程序有两个问题。
首先,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 只发生在替换的直接上下文中,class 模板参数不在函数模板替换的直接上下文中。这里的正确模式是:
template <typename T = ObjType> // default to class template parameter
enable_if_t<has_isa<T>> // use the function template parameter to SFINAE
foo() { ... }
修复这两个问题,程序就会按预期运行。