SFINAE 判断一个类型是否有一个方法
SFINAE to determine if a type has a method
template <typename T>
struct has_xxx {
private:
using true_type = char;
using false_type = long;
template <typename C>
static true_type has_xxx_impl(decltype(&C::xxx)); // comment 1
template <typename C>
static false_type has_xxx_impl(...); // comment 2
public:
enum { value = sizeof(has_xxx_impl<T>(0)) == sizeof(true_type) }; // comment3
};
struct Foo { int xxx() {return 0;}; };
struct Foo2 {};
int main() {
static_assert(has_xxx<Foo>::value, "");
}
这是一个结构体,用来检测一个结构体是否有某个方法。
我对代码有一些疑问。
- 评论1中,这个'&C'是什么意思,为什么我不能只写'C::xxx'
- 评论2中,参数'...'是什么意思,是一个参数包,还是代表任何一种参数
- 在评论 3 中,has_xxx_impl(0) 是如何工作的? T换成了Foo,那么参数0呢?为什么选择第一个函数?
以下解释不完善,不规范。我尽量解释得通俗易懂。
has_xxx_impl(decltype(&C::xxx))
接受由 decltype(&C::xxx)
生成的类型
&C::xxx
是指向成员的指针,decltype(&C::xxx)
是它的类型。
另一个has_xxx_impl(...)
接受"anything"
所以,当你有 has_xxx_impl<T>
编译器时,必须选择以上之一。它更喜欢更好的匹配。因此,当类型包含 xxx
时,第一个比 ...
版本更匹配。如果不包含该成员,则无法选择该函数,必须使用另一个(...
)
感谢 SFINAE
- 替换失败(尝试传递 non-existent 成员的类型时)不会产生错误(选择了其他重载)
注意,这两个函数 returns 不同类型(true_type
和 false_type
被定义为具有不同的大小)所以使用 sizeof(has_xxx_impl<T>(0)) == sizeof(true_type)
你可以分辨出哪一个被选中。此比较的结果设置为 value
,您可以从外部访问它。
In comment 1, what does this '&C' mean, why can't I just write 'C::xxx'
&
实际上指的是 &C::xxx
的 xxx
部分。也就是说,在 C
中获取成员 xxx
的地址(希望它是一个函数,而不是静态成员 --- 稍后会详细介绍)
In comment 2, what does the parameter '...' mean, is it a parameter pack, or it represents any kind of parameter
...
或省略号运算符是接受任何内容的 variadic argument。这是一种很好的 C-style 做事方式,而且通常不是很安全。您今天最常看到的地方是吞噬异常:
try{
ThrowableFunctionCall();
} catch(...) // swallow any exceptions
{}
(注意:这不是参数包,也不是折叠表达式的一部分。请参阅§5.2.2/6)
In comment 3, how does has_xxx_impl(0) work? T is replaced by Foo, then how about the parameter 0? why the first function is selected?
sizeof(has_xxx_impl<T>(0)) == sizeof(true_type)
是使用 overload resolution 将值设置为 true_type
或 false_type
的 sort-of 巧妙方法。请允许我解释一下:
首先调用 has_xxx_impl<T>
看起来
template <typename C>
static true_type has_xxx_impl(decltype(&C::xxx)); // comment 1
template <typename C>
static false_type has_xxx_impl(...); // comment 2
是可行的候选者,因为它们具有相同的名称,并且可能都可以用类型 T
实例化。然而
decltype(&C::xxx)
正在尝试创建一个指向成员类型的指针(例如 int(C::*)()
用于成员函数指针)并且它是在 type-deduced 上下文中这样做的。如果语句是 ill-formed,那么 SFINAE 生效并使它成为一个糟糕的候选者,只剩下
template <typename C>
static false_type has_xxx_impl(...); // comment 2
并且由于 ...
匹配任何内容,因此该值设置为 false_type
或 long
。
如果语句是well-formed,那么0
可以适当地分配给一个函数指针类型(它被不情愿地转换为具有null
值的指针类型)。此转换优于匹配 ...
(try it yourself!),因此值采用 true_type
,即 char
.
最后,
value = sizeof(has_xxx_impl<T>(0)) == sizeof(true_type)
我们可以说是将 char
的大小与 long
的大小进行比较(没有 xxx
的情况)
或尺寸 long
到尺寸 long
(有一个 xxx
大小写)。
请注意,所有这些在 C++11/14 中都是完全不必要的,如果 xxx
是成员变量而不是函数,则确实会失败:
struct Foo3{static const int xxx;};
的评论虽然简单,但也有类似的失败。
更好的解决方案是使用 is_member_function_pointer
类型特征:
template<typename...> // parameter pack here
using void_t = void;
template<typename T, typename = void>
struct has_xxx : std::false_type {};
template<typename T>
struct has_xxx<T, void_t<decltype(&T::xxx)>> :
std::is_member_function_pointer<decltype(&T::xxx)>{};
template <typename T>
struct has_xxx {
private:
using true_type = char;
using false_type = long;
template <typename C>
static true_type has_xxx_impl(decltype(&C::xxx)); // comment 1
template <typename C>
static false_type has_xxx_impl(...); // comment 2
public:
enum { value = sizeof(has_xxx_impl<T>(0)) == sizeof(true_type) }; // comment3
};
struct Foo { int xxx() {return 0;}; };
struct Foo2 {};
int main() {
static_assert(has_xxx<Foo>::value, "");
}
这是一个结构体,用来检测一个结构体是否有某个方法。 我对代码有一些疑问。
- 评论1中,这个'&C'是什么意思,为什么我不能只写'C::xxx'
- 评论2中,参数'...'是什么意思,是一个参数包,还是代表任何一种参数
- 在评论 3 中,has_xxx_impl(0) 是如何工作的? T换成了Foo,那么参数0呢?为什么选择第一个函数?
以下解释不完善,不规范。我尽量解释得通俗易懂。
has_xxx_impl(decltype(&C::xxx))
接受由 decltype(&C::xxx)
生成的类型
&C::xxx
是指向成员的指针,decltype(&C::xxx)
是它的类型。
另一个has_xxx_impl(...)
接受"anything"
所以,当你有 has_xxx_impl<T>
编译器时,必须选择以上之一。它更喜欢更好的匹配。因此,当类型包含 xxx
时,第一个比 ...
版本更匹配。如果不包含该成员,则无法选择该函数,必须使用另一个(...
)
感谢 SFINAE
- 替换失败(尝试传递 non-existent 成员的类型时)不会产生错误(选择了其他重载)
注意,这两个函数 returns 不同类型(true_type
和 false_type
被定义为具有不同的大小)所以使用 sizeof(has_xxx_impl<T>(0)) == sizeof(true_type)
你可以分辨出哪一个被选中。此比较的结果设置为 value
,您可以从外部访问它。
In comment 1, what does this '&C' mean, why can't I just write 'C::xxx'
&
实际上指的是 &C::xxx
的 xxx
部分。也就是说,在 C
中获取成员 xxx
的地址(希望它是一个函数,而不是静态成员 --- 稍后会详细介绍)
In comment 2, what does the parameter '...' mean, is it a parameter pack, or it represents any kind of parameter
...
或省略号运算符是接受任何内容的 variadic argument。这是一种很好的 C-style 做事方式,而且通常不是很安全。您今天最常看到的地方是吞噬异常:
try{
ThrowableFunctionCall();
} catch(...) // swallow any exceptions
{}
(注意:这不是参数包,也不是折叠表达式的一部分。请参阅§5.2.2/6)
In comment 3, how does has_xxx_impl(0) work? T is replaced by Foo, then how about the parameter 0? why the first function is selected?
sizeof(has_xxx_impl<T>(0)) == sizeof(true_type)
是使用 overload resolution 将值设置为 true_type
或 false_type
的 sort-of 巧妙方法。请允许我解释一下:
首先调用 has_xxx_impl<T>
看起来
template <typename C>
static true_type has_xxx_impl(decltype(&C::xxx)); // comment 1
template <typename C>
static false_type has_xxx_impl(...); // comment 2
是可行的候选者,因为它们具有相同的名称,并且可能都可以用类型 T
实例化。然而
decltype(&C::xxx)
正在尝试创建一个指向成员类型的指针(例如 int(C::*)()
用于成员函数指针)并且它是在 type-deduced 上下文中这样做的。如果语句是 ill-formed,那么 SFINAE 生效并使它成为一个糟糕的候选者,只剩下
template <typename C>
static false_type has_xxx_impl(...); // comment 2
并且由于 ...
匹配任何内容,因此该值设置为 false_type
或 long
。
如果语句是well-formed,那么0
可以适当地分配给一个函数指针类型(它被不情愿地转换为具有null
值的指针类型)。此转换优于匹配 ...
(try it yourself!),因此值采用 true_type
,即 char
.
最后,
value = sizeof(has_xxx_impl<T>(0)) == sizeof(true_type)
我们可以说是将 char
的大小与 long
的大小进行比较(没有 xxx
的情况)
或尺寸 long
到尺寸 long
(有一个 xxx
大小写)。
请注意,所有这些在 C++11/14 中都是完全不必要的,如果 xxx
是成员变量而不是函数,则确实会失败:
struct Foo3{static const int xxx;};
更好的解决方案是使用 is_member_function_pointer
类型特征:
template<typename...> // parameter pack here
using void_t = void;
template<typename T, typename = void>
struct has_xxx : std::false_type {};
template<typename T>
struct has_xxx<T, void_t<decltype(&T::xxx)>> :
std::is_member_function_pointer<decltype(&T::xxx)>{};