简体   繁体   中英

Checking for existence of an (overloaded) member function

There are a number of answered questions about checking whether a member function exists: for example, Is it possible to write a template to check for a function's existence?

But this method fails, if the function is overloaded. Here is a slightly modified code from that question's top-rated answer.

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

This code prints out:

0
0

But:

1
0

if the second helloworld() is commented out.

So my question is whether it's possible to check whether a member function exists, regardless of whether it's overloaded.

In C++ it impossible [so far] to take the address of an overload set: when you take the address of a function or a member function the function is either unique or it is necessary to have the appropriate pointer be chosen, eg, by passing the pointer immediately to a suitable function or by casting it. Put differently, the expression &C::helloworld fails if helloworld isn't unique. As far as I know the result is that it is not possible to determine whether a possibly overloaded name is present as a class member or as a normal function.

Typically you'll need to do something with the name, however. That is, if it is sufficient to know if a function is present and can be called with a set of arguments of specified type, the question becomes a lot different: this question can be answered by attempting a corresponding call and determining its type in a SFINAE-able context, eg:

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

You'd then use this type to determine if there is a member which can suitably be called, eg:

std::cout << std::boolalpha
          << has_helloworld<Hello>::value << '\n'       // false
          << has_helloworld<Hello, int>::value << '\n'  // true
          << has_helloworld<Generic>::value << '\n';    // false
// 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

I'd put the above in a details namespace, and then expose a template<class T, class Sig> struct has_helloworld:details::has_helloworld<T,Sig>{}; so someone doesn't pass a type in place of the defaulted void .

We use SFINAE to detect if we can invoke T.helloworld(Args...) . If the passed in signature is void(blah) , we just detect if the call can occur -- if not, we test that the return type of T.helloworld(Args...) can be converted into the return type of the signature.

MSVC has significant issues doing SFINAE with decltype , so the above may not work in MSVC.

Note that has_helloworld<T, R(Args...)> corresponds to passing in an rvalue T , invoking helloworld in that rvalue context passing it rvalue Args... . To make the values lvalues, add & . To make them const lvalues, use const& on the types. However, this should mostly only matter in some corner cases.

For the more general case, there is no way to detect the existence of an overridden method without having a sample signature to match.

The above can be adapted to handle exact signature matches.

Amusingly, if there is a signature conflict (ie, an error would occur in the immediate context of the call), it acts as if there is no such method.

As it relies on SFINAE, errors in the non-immediate context do not trigger failure.

As other answers said, generally, it's impossible.

But, if you can "make" the class writer to always inherit from a predefined base class, and you check "something" existence of that name instead of "member function" existence of that name, perhaps you can do this:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM