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

时间:2014-10-14 14:25:14

标签: c++ c++11 metaprogramming variadic-templates c++14

因此,如果成员函数存在,我非常熟悉测试范例。目前此代码有效:

#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都会产生失败的静态断言。

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

1 个答案:

答案 0 :(得分:8)

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

无法从nullptr_t推断添加到其末尾的内容,因此编译器会说“我放弃,而不是这种情况”。

But we don't need to have Arg... in a deducable context for your trick to work

#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>;

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