R. Absil
R. Absil

Reputation: 243

Forwarding member function arguments

Thing simple enough, I want to forward the call of a member function, along with its arguments, as described in the following snippet.

Please note that this is not a duplicate of this question, nor this one.

#include <iostream>
#include <functional>

template<class Function, class ... Args>
auto forward_args(Function&& f, Args&& ... args)
{
    return f(std::forward<Args>(args)...);
}

int f(int i) { return i; }

struct A {
  int get(int i) const { return i; }  
};

int main()
{
    std::cout << forward_args(f, 2) << std::endl; //ok
    
    A a;
    //std::cout << forward_args(&A::get, a, 2) << std::endl; //ko
        
    static auto wrong_wrapper = &A::get;
    //std::cout << forward_args(wrong_wrapper, a, 2) << std::endl; //ko again
        
    static std::function<int (const A&, int)> wrapper = &A::get;
    std::cout << forward_args(wrapper, a, 2) << std::endl;
}

The commented lines in the main function don't compile (g++ 10.2.0 -- error: must use ‘.*’ or ‘->*’ to call pointer-to-member function in ‘f (...)’, e.g. ‘(... ->* f) (...)’)

I don't quite understand what the compiler is trying to tell me, considering the last cll with the std::function wrapper does work. And, beside fixing the code, I'd also like to know why it doesn't work.

Upvotes: 1

Views: 1147

Answers (3)

R. Absil
R. Absil

Reputation: 243

Alright, first thing, I still don't understand why forward_args(&A::get, a, 2) doesn't work. Part of the answer was "you need this", but I actually provide it with the second parameter, right ? How is that different from the std::function wrapper ?

On the other hand, while the workarounds proposed in above answer work on the snippet, I actually simplified my original problem too much. I actually need to launch tasks asynchronously, in the following code

  • thread safety has been removed
  • yeah, I want to pack all calls in a single data structure, namely tasks, which is wy I start building up wrappers
  • I don't understand how I can use the proposed solutions to the code below.

#include <iostream>
#include <future>
#include <functional>
#include <queue>

std::queue<std::function<void()>> tasks;

template<class Function, class ... Args>
auto enqueue(Function&& f, Args&& ... args) -> std::future<decltype(f(args...))>
{
    std::function<decltype(f(args...))()> func = std::bind(std::forward<Function>(f), std::forward<Args>(args)...);
    auto task_ptr = std::make_shared<std::packaged_task<decltype(f(args...))()>>(func);

    std::function<void()> wrapper = [task_ptr]() //wrapper to match types of 'tasks'... ugly
    {
        (*task_ptr)();
    };
        
    tasks.push(wrapper);        

    return task_ptr->get_future();
}

void indep() {}

struct A {
  int get(int i) const { return i; }  
};

int main()
{
    enqueue(indep);
    
    A a;
    //enqueue(&A::get, a, 2); //wrong
    
    static auto wrapper_wrong = &A::get;
    //enqueue(wrapper_wrong, a, 2); //wrong again
    
    static std::function<int(const A&,int)> wrapper = &A::get;
    enqueue(wrapper, a, 2); //ok
    
    static auto autoptr = std::mem_fn(&A::get);
    enqueue(autoptr, a, 2); //ok again
}

Upvotes: 0

nvevg
nvevg

Reputation: 180

Calling a member function through pointer-to-member still requires this pointer, as in usual (direct) invocations. Simply put, you could succeeded calling A::get() like

static auto wrong_wrapper = &A::get;
(a.*wrong_wrapper)(2);

but what you got after forward_args was instantiated is

A::get(a, 2);

which is not the correct syntax in its nature.

Solution

As it has been already said in the comments section, if you are allowed to use C++17, employ std::invoke. If you aren't, you can work it around using std::reference_wrapper, which accepts any callable type.

template<class Function, class ... Args>
auto forward_args(Function f, Args&& ... args)
{
    return std::ref(f)(std::forward<Args>(args)...);
}

I don't forward f here because std::reference_wrapper requires that the object passed is not an rval.

UPD:

Don't forget to specify the trailing return type of forward_args if you use C++11

template<class Function, class ... Args>
auto forward_args(Function f, Args&& ... args) -> decltype(std::ref(f)(std::forward<Args>(args)...))
{
    return std::ref(f)(std::forward<Args>(args)...);
}

Upvotes: 5

rafix07
rafix07

Reputation: 20938

std::function works because it uses std::invoke which handles calling pointer to member function.

As the solution you could write:

template<class Function, class ... Args>
auto forward_args(Function&& f, Args&& ... args) {
    return std::invoke(std::forward<Function>(f), std::forward<Args>(args)...);
}

Syntax for calling member function for an object are:

obj.memberFunction();
obj->memberFunction();

or if you have a pointer to member function:

using Ptr = int (A::*)(int) const;
Ptr p = &A::get;
A a;
(a.*p)(1);     // [1]
(obj.*memberFuncPtr)(args...);

the line [1] is valid syntax for calling member function pointed by a pointer. In your case you try A::get(a,2) which is just not valid and cannot work.

Upvotes: 1

Related Questions