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. 评论1中,这个'&C'是什么意思,为什么我不能只写'C::xxx'
  2. 评论2中,参数'...'是什么意思,是一个参数包,还是代表任何一种参数
  3. 在评论 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_typefalse_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::xxxxxx 部分。也就是说,在 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_typefalse_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_typelong

如果语句是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)>{};

Live Demo