Checking for existence of an (overloaded) member function
Asked Answered
M

3

5

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.

Millennial answered 3/2, 2015 at 21:31 Comment(7)
Do you want to know if a function exists if called with certain parameters? What compilers do you need to support? What standard versions?Dg
It's enough to know that it exists with any parameters. I'm happy with C++11, but having said that I'd like to support as many compilers as possible.Millennial
Speaking of compilers, I just noticed that the code as given compiles and prints 0 0 with Clang 3.5.1, but it doesn't compile with GCC 4.9.2.Millennial
I don't care if it is sufficient to know that. Is it sufficient to answer "if I called the symbol foo with two parameters that are ints, would it work", or not? Because that is a different problem than the other one. And: "As many compilers as possible" -- please be more specific. C++11 support other than gcc/clang is very spotty.Dg
You don't care about what? I want to know if the function exists with any parameters, but if you give me with certain parameters that will also work. Let's go with GCC and Clang.Millennial
Also, changing typeof to decltype makes both GCC and Clang happy with the above code (but they still produce 0 0). I've updated the code sample to use decltype.Millennial
I think this does mean we can't write a perfect type trait testing if a type is valid as the type of an expression in a range for statement for (auto&& var : RANGE);. We can test validity of the expression declval<RangeT &>().begin(), but not the validity of a statement or the existence of any member named begin, and the Standard says the loop's behavior depends on existence of any members begin and end. So given a strange type with invalid member begin but valid ADL non-member begin, the trait could call it valid when it's actually invalid.Isothere
C
18

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, e.g., 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, e.g.:

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, e.g.:

std::cout << std::boolalpha
          << has_helloworld<Hello>::value << '\n'       // false
          << has_helloworld<Hello, int>::value << '\n'  // true
          << has_helloworld<Generic>::value << '\n';    // false
Commentator answered 3/2, 2015 at 22:3 Comment(1)
Why is the 0 argument necessary (and thus also the unused int parameter)?Mccullers
D
4
// 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.

Dg answered 3/2, 2015 at 21:52 Comment(2)
I liked the ability to differentiate based on return types, but I couldn't get it to work with a void return type. Compiler complained about ambiguous template resolution. @Dietmar's answer didn't suffer from this.Dysphagia
@rave add !std::is_same<R,void>{} && to the R version; oops.Dg
M
0

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;
}
Munniks answered 15/7, 2021 at 5:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.