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

时间:2015-02-03 21:31:42

标签: c++ templates c++11 sfinae

关于检查成员函数是否存在,有许多已回答的问题:例如, 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()已被注释掉。

所以我的问题是,是否可以检查成员函数是否存在,无论它是否过载。

3 个答案:

答案 0 :(得分:13)

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

但是,通常您需要对名称执行某些操作。也就是说,如果知道某个函数是否存在就足够了可以用一组指定类型的参数调用,那么问题会变得很多:这个问题可以通过尝试相应的来回答在具有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

答案 1 :(得分:2)

// 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_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...)的返回类型是否可以转换为签名的返回类型。

MSVC在使用decltype进行SFINAE时遇到重大问题,因此上述内容可能无法在MSVC中使用。

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

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

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

有趣的是,如果存在签名冲突(即,在调用的直接上下文中会发生错误),则表现为没有这样的方法。

由于它依赖于SFINAE,非直接上下文中的错误不会触发失败。

答案 2 :(得分:0)

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

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

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