5

Here is the problem I am facing: I have an overloaded function in a class, and I want to pass one of its overloads as a parameter. But when doing so, I get the following error :

"no suitable constructor exists to convert from <unknown-type> to std::function<...>"

Here's a code sample to illustrate that :

#include <functional>
#include <string>

class  Foo
{
private:
    int val1 , val2;
};    

class Bar
{
public:
    void function ( ) {
        //do stuff;
        Foo f;
        function ( f );
    }    
    void function ( const Foo& f ) {
        //do stuff
    }

private:
    //random attribute
    std::string str;    
};


void otherFunction ( std::function<void ( Bar& , const  Foo& ) > function ) {
    Bar b;
    Foo f;
    function(b,f);
}

int main ( ) {    
    otherFunction ( &Bar::function );
                    ^^^
                   error
}

I understand that the compiler cannot deduce which overload to use, so the next best thing to do is a static_cast, but the following code still has the same error

std::function<void ( Bar& , const Foo& )> f = static_cast< std::function<void ( Bar& , const Foo& )> > ( &Bar::function );
JeJo
  • 30,635
  • 6
  • 49
  • 88
Midnight Exigent
  • 615
  • 4
  • 15

3 Answers3

8

You need to cast to member-function pointer, not to std::function:

otherFunction ( static_cast<void(Bar::*)(const Foo&)>(&Bar::function) );

Live

[EDIT]

Explanation:

otherFunction ( &Bar::function );

otherFunction takes std::function as a parameter. std::function has an implicit constructor (an implicit conversion) from a function pointer (a member function or a free function, and other "callable" types, doesn't matter here). It looks like this:

template< class F > 
function( F f );
  • it's a template parameter
  • while F is "callable", it doesn't specify the signature of F

This means that compiler doesn't know which Bar::function you meant, because this constructor doesn't put any restrictions on input parameter. That's what compiler is complaining about.

You tried

static_cast< std::function<void ( Bar& , const Foo& )> > ( &Bar::function );

While it looks like compiler has all details it needs here (the signature), actually the same constructor is called, so nothing effectively changed. (Actually, the signature is incorrect, but even correct one wouldn't work)

By casting to a function pointer we provide its signature

static_cast<void(Bar::*)(const Foo&)>(&Bar::function)

So ambiguity is resolved as there's only one such function so compiler is happy.

Andriy Tylychko
  • 15,967
  • 6
  • 64
  • 112
3

If you use the typed member function pointer along with a templated otherFunction, your code will work. That means, change your otherFunction() to:

template<typename Class, typename T>
void otherFunction(void(Class::*)(T) ) {
    Bar b;
    Foo f;
    b.function(f);
}

If the syntax is confusing, use a helper (template)alias for member function pointer:

template<typename Class, typename T>
using MemFunPtr = void(Class::*)(T);

template<typename Class, typename T>
void otherFunction(MemFunPtr<Class, T> function) {
    Bar b;
    Foo f;
    b.function(f);
}

Now you can call the function without typecasting.

int main()
{
    otherFunction(&Bar::function);
    return 0;
}

(See Online)

JeJo
  • 30,635
  • 6
  • 49
  • 88
2

You may need to use a macro in order to avoid a few headaches.

#define OVERLOADED(f) \
  [&](auto&&...xs)noexcept->decltype(auto)\
    {return std::invoke(f, decltype(xs)(xs)...);}

int main(){
  g(OVERLOADED(&Bar::function));
}
David G
  • 94,763
  • 41
  • 167
  • 253
  • If you are going to do that, you should use perfect forwarding. I.e. `return std::invoke(f, std::forward(xs)...);` – Adrian Jul 16 '19 at 18:02
  • @Adrian That is what `decltype(xs)(xs)` does. – David G Jul 16 '19 at 18:04
  • If `decltype(xs)` results in an rvalue, it would create a new temporary. That is why there is a `std::forward` function. – Adrian Jul 16 '19 at 18:06
  • @Adrian `xs` is received through a forwarding reference (`auto&&`), so `decltype(xs)` will either be `T&` or `T&&`. There are **no** temporaries being made. – David G Jul 16 '19 at 18:08
  • No, if `xs` binds to an lvalue reference, `decltype(xs)` would be an lvalue reference type, but if `xs` binds to an rvalue reference, `decltype(xs)` would be an rvalue type. Try it. – Adrian Jul 16 '19 at 18:10
  • @Adrian Could you show me then? Because the errors in [this example](https://coliru.stacked-crooked.com/a/f67caeaae604d04d) show otherwise. – David G Jul 16 '19 at 18:13
  • I stand corrected. I was thinking in terms of the template type in template type deduction, but the variable itself still has the appropriate reference type. My apologies. – Adrian Jul 16 '19 at 18:27