【问题标题】:SFINAE to determine if a type has a methodSFINAE 判断一个类型是否有方法
【发布时间】:2016-01-19 06:43:09
【问题描述】:
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, "");
}

这是一个struct,用来检测一个struct是否有某个方法。 我对代码有一些疑问。

  1. 在评论 1 中,这个 '&C' 是什么意思,为什么我不能只写 'C::xxx'
  2. 在注释2中,参数'...'是什么意思,是参数包,还是代表任何类型的参数
  3. 在评论 3 中,has_xxx_impl(0) 是如何工作的? T换成Foo,那么参数0呢?为什么选择第一个函数?

【问题讨论】:

标签: c++ c++11 c++14 template-meta-programming


【解决方案1】:

在评论1中,这个'&C'是什么意思,为什么我不能写'C::xxx'

&amp; 实际上是指&amp;C::xxxxxx 部分。也就是说,在 C 中获取成员 xxx 的地址(希望是一个函数,而不是静态成员 --- 稍后会详细介绍)

​​>

在注释2中,参数'...'是什么意思,是参数包,还是代表任何一种参数

... 或省略号运算符是接受任何内容的variadic argument。这是一种非常 C 风格的做事方式,通常不是很安全。今天你看到最多的地方是吞咽异常:

try{
ThrowableFunctionCall();
} catch(...) // swallow any exceptions
{}

(注意:这不是参数包,也不是折叠表达式的一部分。请参阅 §5.2.2/6)

在评论 3 中,has_xxx_impl(0) 是如何工作的? T换成Foo,那么参数0呢?为什么选择第一个函数?

sizeof(has_xxx_impl&lt;T&gt;(0)) == sizeof(true_type) 是一种使用overload resolution 将值设置为true_typefalse_type 的巧妙方法。请允许我解释一下:

一开始调用has_xxx_impl&lt;T&gt; 看起来两者都是

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::*)() 用于成员函数指针)并且它是在类型推断的上下文中这样做的。如果语句格式不正确,则 SFINAE 生效并使其成为不好的候选者,我们只剩下

template <typename C>
static false_type has_xxx_impl(...); // comment 2

由于... 匹配任何内容,因此该值设置为false_typelong

如果语句格式正确,则可以将0 正确分配给函数指针类型(不情愿地将其转换为具有null 值的指针类型)。此转换优先于匹配... (try it yourself!),因此该值采用true_type,即char

最后,

value = sizeof(has_xxx_impl&lt;T&gt;(0)) == sizeof(true_type)

我们可以说是将char 的大小与long 的大小进行比较(没有xxx 案例) 或long 的大小到long 的大小(具有xxx 大小写)。

请注意,所有这些在 C++11/14 中都是完全不必要的,如果 xxx 是成员变量而不是函数,则确实会失败:

struct Foo3{static const int xxx;};

melak47 的评论虽然简单,但也有类似的缺点。

更好的解决方案是使用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

【讨论】:

    【解决方案2】:

    以下解释不完善,不引用标准。我尽量解释得通俗易懂。

    has_xxx_impl(decltype(&amp;C::xxx)) 接受由decltype(&amp;C::xxx) 生成的类型
    &amp;C::xxx 是指向成员的指针,decltype(&amp;C::xxx) 是它的类型。

    另一个has_xxx_impl(...) 接受“任何东西”

    所以,当你有has_xxx_impl&lt;T&gt; 编译器必须选择以上之一。它更喜欢更好的匹配。因此,当 type 包含 xxx 时,第一个比 ... 版本更好。如果不包含该成员,则无法选择该功能,必须使用另一个(...

    感谢SFINAE - 替换失败(尝试传递不存在成员的类型时)不会产生错误(选择了其他重载)

    请注意,这两个函数都返回不同的类型(true_typefalse_type,它们被定义为具有不同的大小),因此使用 sizeof(has_xxx_impl&lt;T&gt;(0)) == sizeof(true_type) 您可以知道选择了哪一个。此比较的结果设置为value,您可以从外部访问它。

    【讨论】:

    • is a member function pointer 它是“指向成员的指针”,并非专门用于功能。它也可以指向数据成员
    • decltype(&C::xxx)和decltype(C::xxx)有什么区别。我试过 decltype(C::xxx),它只能检测到像 struct { int xxx; 这样的成员变量},它无法检测到像 struct { int xxx() {}; 这样的成员函数}。但是 decltype(&C::xxx) 可以同时检测函数和变量。
    • @SlardarZhang 您不能按值传递函数。此外,它是一个指针这一事实也很重要。如果您不使用指针,那么 std::string xxx 之类的东西也不起作用。通过获取指针,传递给函数的0 可以转换为它。
    猜你喜欢
    • 1970-01-01
    • 2018-09-15
    • 1970-01-01
    • 2021-02-28
    • 2012-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多