使用variadics测试成员函数是否存在

Testing if member function exists using variadics

本文关键字:是否 存在 函数 成员 variadics 测试 使用      更新时间:2023-10-16

所以我非常熟悉测试成员函数是否存在的范例。目前此代码有效:

#include <iostream>
#include <type_traits>
struct has_mem_func_foo_impl {
    template <typename U, U>
    struct chk { };
    template <typename Class, typename Arg>
    static std::true_type has_foo(chk<void(Class::*)(Arg), &Class::foo>*);
    template <typename, typename>
    static std::false_type has_foo(...);
};
template <typename Class, typename Arg>
struct has_mem_func_foo : decltype(has_mem_func_foo_impl::template has_foo<Class,Arg>(nullptr)) { };

struct bar {
    void foo(int) { }
};
int main() {
    static_assert( has_mem_func_foo<bar, int>::value, "bar has foo(int)" );
}

不幸的是,如果我稍微调整一下:

#include <iostream>
#include <type_traits>
struct has_mem_func_foo_impl {
    template <typename U, U>
    struct chk { };
    template <typename Class, typename... Arg>
    static std::true_type has_foo(chk<void(Class::*)(Arg...), &Class::foo>*);
    template <typename, typename...>
    static std::false_type has_foo(...);
};
template <typename Class, typename... Arg>
struct has_mem_func_foo : decltype(has_mem_func_foo_impl::template has_foo<Class,Arg...>(nullptr)) { };

struct bar {
    void foo(int) { }
};
int main() {
    static_assert( has_mem_func_foo<bar, int>::value, "bar has foo(int)" );
}

我的静态断言失败了。我的印象是,变量模板参数包在扩展到它们的位置时会得到相同的处理。gcc和clang都会产生一个失败的静态断言。

因此,我问题的真正根源在于,这是标准行为吗?当测试可变模板成员函数的存在时,它也会失败。

我看到的问题是Arg...被传递给int是不够的。编译器在其末尾添加新的args是有效的。

nullptr_t中推导出要添加到它末尾的内容是不可能的,所以编译器说"我放弃了,不是这种情况"。

但是,我们不需要在可重复数据消除的上下文中使用Arg...,就可以让您的技巧发挥作用:

#include <iostream>
#include <type_traits>
template<class Sig>
struct has_mem_func_foo_impl;
template<class R, class...Args>
struct has_mem_func_foo_impl<R(Args...)> {
  template <typename U, U>
  struct chk { };
  template <typename Class>
  static constexpr std::true_type has_foo(chk<R(Class::*)(Args...), &Class::foo>*) { return {}; }
  template <typename>
  static constexpr std::false_type has_foo(...) { return {}; }
};
template <typename Class, typename Sig>
struct has_mem_func_foo :
  decltype(has_mem_func_foo_impl<Sig>::template has_foo<Class>(nullptr))
{};
struct bar {
  void foo(int) { }
};

int main() {
  static_assert( has_mem_func_foo<bar, void(int)>::value, "bar has foo(int)" );
}

我们将Args...移到类本身,然后只将类型传递给函数。这阻止了推导,这使得nullptr到成员函数指针的转换是可行的,并且事情又正常了。

我还包含了一些改进的基于签名的语法,这也意味着它支持返回类型匹配。

请注意,你可能问错了问题。您正在询问是否存在具有特定签名的成员函数:通常您想知道的是,是否存在可使用某组参数调用的成员函数,其返回类型与您的返回值兼容。

namespace details {
  template<class T, class Sig, class=void>
  struct has_foo:std::false_type{};
  template<class T, class R, class... Args>
  struct has_foo<T, R(Args...),
    typename std::enable_if<
      std::is_convertible<
        decltype(std::declval<T>().foo(std::declval<Args>()...)),
        R
      >::value
      || std::is_same<R, void>::value // all return types are compatible with void
      // and, due to SFINAE, we can invoke T.foo(Args...) (otherwise previous clause fails)
    >::type
  >:std::true_type{};
}
template<class T, class Sig>
using has_foo = std::integral_constant<bool, details::has_foo<T, Sig>::value>;

尝试调用T.foo(int)并检查返回值是否兼容。

为了好玩,我使has_foo的类型实际上是true_typefalse_type,而不是继承自。我本可以:

template<class T, class Sig>
using has_foo = details::has_foo<T, Sig>;

如果我不想要那个额外的功能。