检查是否存在(重载的)成员函数

Checking for existence of an (overloaded) member function

有很多关于检查成员函数是否存在的回答问题:例如, Is it possible to write a template to check for a function's existence?

但如果函数重载,此方法将失败。这是对该问题的最高评价答案稍作修改的代码。

#include <iostream>
#include <vector>

struct Hello
{
    int helloworld(int x)  { return 0; }
    int helloworld(std::vector<int> x) { return 0; }
};

struct Generic {};


// SFINAE test
template <typename T>
class has_helloworld
{
    typedef char one;
    typedef long two;

    template <typename C> static one test( decltype(&C::helloworld) ) ;
    template <typename C> static two test(...);


public:
    enum { value = sizeof(test<T>(0)) == sizeof(char) };
};


int
main(int argc, char *argv[])
{
    std::cout << has_helloworld<Hello>::value << std::endl;
    std::cout << has_helloworld<Generic>::value << std::endl;
    return 0;
}

这段代码打印出来:

0
0

但是:

1
0

如果第二个helloworld()被注释掉。

所以我的问题是是否可以检查成员函数是否存在,而不管它是否重载。

// use std::void_t in C++... 17 I think? ... instead of this:
template<class...>struct void_type { using type = void; };
template<class...Ts>using void_t = typename void_type<Ts...>::type;

template<class T, class...Args>
using hello_world_ify = decltype(
      std::declval<T>().helloworld( std::declval<Args>()... )
);

template<class T, class Sig, class=void>
struct has_helloworld:std::false_type{};

template<class T, class...Args>
struct has_helloworld<T, void(Args...),
  void_t<
    hello_world_ify<T, Args...>
  >
>:std::true_type{};

template<class T, class R, class...Args>
struct has_helloworld<T, R(Args...),
  typename std::enable_if<
    !std::is_same<R,void>::value &&
    std::is_convertible<
      hello_world_ify<T, Args...>,
      R
    >::value
  >::type
>:std::true_type{};

live example

我将上面的内容放在 details 命名空间中,然后公开一个 template<class T, class Sig> struct has_helloworld:details::has_helloworld<T,Sig>{}; 这样就不会有人传递类型来代替默认的 void

我们使用 SFINAE 来检测我们是否可以调用 T.helloworld(Args...)。如果传入的签名是void(blah),我们只是检测是否可以调用——如果不能,我们测试T.helloworld(Args...)的return类型是否可以转换为return ] 签名类型。

MSVC 在使用 decltype 执行 SFINAE 时存在重大问题,因此上述内容在 MSVC 中可能不起作用。

请注意,has_helloworld<T, R(Args...)> 对应于传入右值 T,在该右值上下文中调用 helloworld 并传递右值 Args...。要使值成为左值,请添加 &。要使它们成为常量左值,请在类型上使用 const&。但是,这应该主要只在某些极端情况下才重要。

对于更一般的情况,如果没有匹配的样本签名,就无法检测到重写方法的存在。

上面的内容可以用来处理精确的签名匹配。

有趣的是,如果存在签名冲突(即在调用的直接上下文中会发生错误),它就好像没有这样的方法一样。

由于它依赖于 SFINAE,因此非即时上下文中的错误不会触发失败。

在 C++ 中,[到目前为止] 无法获取重载集的地址:当您获取函数或成员函数的地址时,该函数要么是唯一的,要么必须选择适当的指针,例如,通过立即将指针传递给合适的函数或对其进行强制转换。换句话说,如果 helloworld 不唯一,则表达式 &C::helloworld 失败。据我所知,结果是无法确定可能重载的名称是作为 class 成员还是作为普通函数存在。

但是,通常您需要对名称进行一些处理。也就是说,如果知道函数是否存在 是否可以用一组指定类型的参数调用就足够了,问题就变得很不一样了:这个问题可以通过以下方式回答尝试相应的调用并在支持 SFINAE 的上下文中确定其类型,例如:

template <typename T, typename... Args>
class has_helloworld
{
    template <typename C,
              typename = decltype( std::declval<C>().helloworld(std::declval<Args>()...) )>
    static std::true_type test(int);
    template <typename C>
    static std::false_type test(...);

public:
    static constexpr bool value = decltype(test<T>(0))::value;
};

然后您将使用此类型来确定是否存在可以适当调用的成员,例如:

std::cout << std::boolalpha
          << has_helloworld<Hello>::value << '\n'       // false
          << has_helloworld<Hello, int>::value << '\n'  // true
          << has_helloworld<Generic>::value << '\n';    // false

正如其他答案所说,一般来说,这是不可能的。

但是,如果您可以“使”class 编写器始终从预定义的基础 class 继承,并且您检查该名称的“某物”是否存在而不是“成员函数”是否存在那个名字,也许你可以这样做:

struct B
{
    static char const helloworld = 0;
};

template <typename D>
std::true_type test_helloworld(...);
template <typename D, typename = std::enable_if_t<&D::helloworld == &B::helloworld>>
std::false_type test_helloworld(int);
template <typename D, typename = std::enable_if_t<std::is_base_of_v<B, D>>>
using has_helloworld = decltype(test_helloworld<D>(0));


struct D1 : B
{
};

struct D2 : B
{
    void helloworld() {}
};

struct D3 : B
{
    void helloworld() {}
    void helloworld(int) {}
};

struct D4 : B
{
    using helloworld = int;
};

struct D5 //no `: B`
{};


int main()
{
    static_assert(has_helloworld<D1>::value == false);
    static_assert(has_helloworld<D2>::value == true);
    static_assert(has_helloworld<D3>::value == true);    
    static_assert(has_helloworld<D4>::value == true);
    //auto v = has_helloworld<D5>::value;//This fails the compile.
    return 0;
}