Ela782
Ela782

Reputation: 5181

Pass overloaded member function to a variadic template function

I have a class with a function add:

class Pool {
public:
    Pool() {};
    template<class F, class... A>
    auto add(F&& f, A&&... args) -> std::future<typename std::result_of<F(A...)>::type>
    {
        // return empty placeholder, for the sake of this code example
        std::future<typename std::result_of<F(A...)>::type> ret;
        return ret;
    };
};

It should take any function with its arguments, add it to a thread pool and return a future of the result type of that function.

And a class where I use it:

class MyClass {
public:
    string doIt(string) { return string("a"); };
    string doIt(int, string) { return string("b"); };

    void test() {
        Pool myPool;
        string a("test");
        myPool.add(&MyClass::doIt, a); // Error
    };
};

Which gives a compiler error:

Error   1   error C2914: 'Pool::add' : cannot deduce template argument as function argument is ambiguous    MyClass.cpp 94

Now the problem is (I think) that the compiler can't deduce which overload I want to use. Similar to Overloaded function as argument of variadic template function. (Also I'm not 100% clear on why I have to use "&" for class member functions, but no ampersand if I pass in a free function). Anyway I also tried the workaround mentioned in above answer:

struct doIt_wrapper {
    template <typename... T>
    auto operator()(T... args) -> decltype(doIt(args...)) {
        return doIt(args...);
    }
};

and then modifying MyClass::test() to:

    void test() {
        Pool myPool;
        string a("test");
        myPool.add(doIt_wrapper(), a);
    };

But it also gives me a compiler error:

error C2893: Failed to specialize function template 'unknown-type doIt_wrapper::operator ()(T...)'  C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\xrefwrap 58

I also tried a few variants like myPool.add(doIt_wrapper<string>() and with/without '&' but they all generate one or the other compiler error.

I think I don't fully understand the problem yet and I would be glad if someone could shed light on it. Also I am looking for a proper solution to this problem. It can't really be the case that this only works as long as there are no two functions with the same name, and as soon as there are, everything breaks down without a proper, generic solution?

Edit: Fixed a few typos and uploaded a minimal example here: http://ideone.com/eX1r1l

Upvotes: 0

Views: 526

Answers (3)

Jarod42
Jarod42

Reputation: 217275

You may use lambda:

myPool.add([this](const std::string& s) {doIt(s);}, a);

or even

myPool.add([this, a]() {doIt(a);});

Currently, you may indicate which overload to use that way:

myPool.add(static_cast<std::string (MyClass::*) (std::string)>(&MyClass::doIt), a);

Note that doIt is a method (not a free function or static function), so you have to call it with an object. If you add static to doIt, you may choose the overload with

myPool.add(static_cast<std::string (*) (std::string)>(&MyClass::doIt), a);

Upvotes: 1

Banan
Banan

Reputation: 444

As others have mentioned, the problem is that doIt() is not callable inside the doIt_wrapper class as it also needs a pointer to the object called on. You could just modify the doIt_wrapper operator() to also take a pointer to the object and pass a pointer to this as first argument to add(). It would then look something like this:

#include <iostream>
#include <future>
using namespace std;

class Pool {
public:
    Pool() {};
    template<class F, class... A>
    auto add(F&& f, A&&... args) -> std::future<typename std::result_of<F&&(A&&...)>::type>
    {
        // return empty placeholder, for the sake of this code example
        std::future<typename std::result_of<F&&(A&&...)>::type> ret;
        return ret;
    };
};

class MyClass {
public:
     string doIt(string) { return string("a"); };
     string doIt(int, string) { return string("b"); };

     struct doIt_wrapper
     {
        template<class T, class... Ts>
        auto operator()(T&& t, Ts&&... args) -> decltype(t->doIt(std::forward<Ts>(args)...))
        {
           return t->doIt(std::forward<Ts>(args)...);
        }
     };

    void test() {
        Pool myPool;
        string a("test");
        myPool.add(doIt_wrapper(), this, a); // No error no more
    };
};

int main() {
   // your code goes here
   MyClass my;
   my.test();
   return 0;
}

This way you don't have to do the casts. The code compiles on both GCC and Clang.

Upvotes: 2

David G
David G

Reputation: 96810

The problem is that non-static member functions have a hidden, implicit this parameter that points to the instance that called it. Your compiler is right to reject it as it doesn't have the correct arguments for the function. Sending in this as an additional bound parameter will work:

 myPool.add(&MyClass::doIt, this, a);
 //                         ^^^^

Using a lamda expression will work as well.

Also, the standard library function std::async() already does what you're trying to do here, and more. Consider using that instead.


Edit: You also need to cast to the correct type to select the correct overload. @Jarod42 already shows you how.

Upvotes: 1

Related Questions