1

I'm working on implementing multi-threading in a project, but am running into a wall when it comes to some more complicated uses of std::async. I want to call a member function on a templated object and pass in an argument as a parameter, but I can't get it to work when the template holds a shared pointer.

It's pretty simple to use std::async on member functions, and even templated member functions. I've seen plenty of answers around stack overflow for this specific usage. I've even run some test cases myself:

#include <thread>
#include <future>
#include <iostream>

class Bar
{
  public:
    Bar () {}
    double data;
};

template <typename T>
class Foo
{
  public:
    Foo () {}
    T set_data (T d) { data = d; return data; }
  private:
    T data;
};

#include "./foo.h"
#include <memory>

int main (int argc, char **argv)
{
  /**
   * Works fine
   */
  Foo<int> foo1;
  auto fut1 = std::async(std::launch::async, &Foo<int>::set_data, &foo1, 42);

  fut1.wait();
  std::cout << fut1.get() << std::endl;

  return 0;
}

This example compiles perfectly fine in gcc 7.4.0 and returns 42 as expected.

My problem comes in when I use shared_ptr in the template. Using the same Foo and Bar classes from above:

#include "./foo.h"
#include <memory>

int main (int argc, char **argv)
{
  /**
   * Doesn't work
   */
  auto foo2 = std::make_shared<Foo<std::shared_ptr<Bar>>>;
  auto br = std::make_shared<Bar>;
  auto fut2 = std::async(std::launch::async, &Foo<std::shared_ptr<Bar>>::set_data, &foo2, bar);

  fut2.wait();
  std::cout << fut2.get()->data << std::endl;

  return 0;
}

I get this error when compiling g++ -pthread test.cpp -o test

test.cpp: In function ‘int main(int, char**)’:
test.cpp:20:94: error: no matching function for call to ‘async(std::launch, std::shared_ptr<Bar> (Foo<std::shared_ptr<Bar> >::*)(std::shared_ptr<Bar>), Foo<std::shared_ptr<Bar> >*, std::shared_ptr<Bar> (*&)())’
   auto fut2 = std::async(std::launch::async, &Foo<std::shared_ptr<Bar>>::set_data, &foo2, bar);
                                                                                              ^
In file included from ./foo.h:2:0,
                 from test.cpp:1:
/usr/include/c++/7/future:1712:5: note: candidate: template<class _Fn, class ... _Args> std::future<typename std::result_of<typename std::decay<_Tp>::type(typename std::decay<_Args>::type ...)>::type> std::async(std::launch, _Fn&&, _Args&& ...)
     async(launch __policy, _Fn&& __fn, _Args&&... __args)
     ^~~~~
/usr/include/c++/7/future:1712:5: note:   template argument deduction/substitution failed:
/usr/include/c++/7/future: In substitution of ‘template<class _Fn, class ... _Args> std::future<typename std::result_of<typename std::decay<_Tp>::type(typename std::decay<_Args>::type ...)>::type> std::async(std::launch, _Fn&&, _Args&& ...) [with _Fn = std::shared_ptr<Bar> (Foo<std::shared_ptr<Bar> >::*)(std::shared_ptr<Bar>); _Args = {Foo<std::shared_ptr<Bar> >*, std::shared_ptr<Bar> (*&)()}]’:
test.cpp:20:94:   required from here
/usr/include/c++/7/future:1712:5: error: no type named ‘type’ in ‘class std::result_of<std::shared_ptr<Bar> (Foo<std::shared_ptr<Bar> >::*(Foo<std::shared_ptr<Bar> >*, std::shared_ptr<Bar> (*)()))(std::shared_ptr<Bar>)>’
/usr/include/c++/7/future:1745:5: note: candidate: template<class _Fn, class ... _Args> std::future<typename std::result_of<typename std::decay<_Tp>::type(typename std::decay<_Args>::type ...)>::type> std::async(_Fn&&, _Args&& ...)
     async(_Fn&& __fn, _Args&&... __args)
     ^~~~~
/usr/include/c++/7/future:1745:5: note:   template argument deduction/substitution failed:
/usr/include/c++/7/future: In substitution of ‘template<class _Fn, class ... _Args> std::future<typename std::result_of<typename std::decay<_Tp>::type(typename std::decay<_Args>::type ...)>::type> std::async(_Fn&&, _Args&& ...) [with _Fn = std::launch; _Args = {std::shared_ptr<Bar> (Foo<std::shared_ptr<Bar> >::*)(std::shared_ptr<Bar>), Foo<std::shared_ptr<Bar> >*, std::shared_ptr<Bar> (*&)()}]’:
test.cpp:20:94:   required from here
/usr/include/c++/7/future:1745:5: error: no type named ‘type’ in ‘class std::result_of<std::launch(std::shared_ptr<Bar> (Foo<std::shared_ptr<Bar> >::*)(std::shared_ptr<Bar>), Foo<std::shared_ptr<Bar> >*, std::shared_ptr<Bar> (*)())>’

I thought this maybe was because the reference & wasn't working in the proper order with all those angle brackets for the templates, so I tried using some parentheses:

#include "./foo.h"
#include <memory>

int main (int argc, char **argv)
{
  /**
   * Doesn't work
   */
  Foo<std::shared_ptr<Bar>> foo2;
  Bar bar;
  auto fut2 = std::async(std::launch::async, &(Foo<std::shared_ptr<Bar>>::set_data), &foo2, bar);

  fut2.wait();
  std::cout << fut2.get().data << std::endl;

  return 0;
}

Which results in a shorter error which I also don't understand.

test.cpp: In function ‘int main(int, char**)’:
test.cpp:20:75: error: invalid use of non-static member function ‘T Foo<T>::set_data(T) [with T = std::shared_ptr<Bar>]’
   auto fut2 = std::async(std::launch::async, &(Foo<std::shared_ptr<Bar>>::set_data), &foo2, bar);

I am confused why the shared pointers all of a sudden make a difference, and I'm guessing it has to do with type deduction? Any help is appreciated.

EDIT

Thanks to those that responded, here is the solution. The parentheses are not required, and some shared_ptrs were missing.

#include "./foo.h"
#include <memory>

int main (int argc, char **argv)
{
  Foo<std::shared_ptr<Bar>> foo2;
  auto bar = std::make_shared<Bar>(2.5);
  auto fut2 = std::async(std::launch::async, &Foo<std::shared_ptr<Bar>>::set_data), &foo2, bar;

  fut2.wait();
  std::cout << fut2.get()->data << std::endl;

  return 0;
}
4
  • 1
    Voting to close as a typo. set_data takes a T. When T is a shared_ptr<Bar>, you can't pass it a Bar. There is no conversion from Bar to shared_ptr<Bar> Commented Jul 9, 2019 at 12:44
  • @NathanOliver Thanks for pointing that out. Made the edit regarding your first comment. The error is still there. Commented Jul 9, 2019 at 12:53
  • 1
    You also have a typo with fut2.get().data. fut2.get() is a shared_ptr<Bar>. You need fut2.get()->data to get the Bar's data. Commented Jul 9, 2019 at 12:56
  • Try foo2.get() instead of &foo2, when calling std::async. But in general just drop this syntax and use lambdas, they are much easier to understand. Commented Jul 9, 2019 at 13:10

4 Answers 4

2

There are some issues, I think that using a lambda might help you clarify what is going on:

int main (int argc, char **argv)
{
  Foo<std::shared_ptr<Bar>> foo2;
  Bar bar;

  auto op = [foo2, bar]() mutable {return foo2.set_data(std::make_shared<Bar>(bar));};
  auto fut2 = std::async(std::launch::async, op);

  fut2.wait();
  std::cout << fut2.get()->data << std::endl;

  return 0;
}

You have to pass to the set_data a shared_ptr to Foo. Secondly, the set_data is not const-qualified, so you need a mutable lambda. Lastly, the future, when returning though get(), will give you a shared_ptr to Bar so you need the operator -> . You can make the code more efficient moving Foo2 and Bar inside the lambda, but I am trying to keep the answer simple, in particular because I do not know if you want, in your use case to re-use Foo2 and Bar, but you can consider moving inside the lambda.

Regarding your specific code, the following is compiling in g++ 9.1 with C++14, see https://godbolt.org/z/DFZLtb

int main (int argc, char **argv)
{
  Foo<std::shared_ptr<Bar>> foo2;
  Bar bar;
  auto fut2 = std::async(std::launch::async, &Foo<std::shared_ptr<Bar>>::set_data, &foo2, std::make_shared<Bar>());

  fut2.wait();
  std::cout << fut2.get()->data << std::endl;

  return 0;
}

You need to provide a shared_ptr<Bar>as argument and not a Bar and you need to remove the parenthesis around Foo<std::shared_ptr<Bar>>::set_data.

Sign up to request clarification or add additional context in comments.

4 Comments

I had also tried a lambda, but in my use case I needed to return something from the function being called by async and I couldn't get the lambda to do that properly. I will give this a shot, however.
Try to play around with my code at godbolt.org/z/IYSLqQ . If you post your tentative with lambdas I can take a look.
Thanks for that! I'm just still hesitant to go to a lambda because I know that this should work somehow by just passing the function. I tried changing what you suggesting to shared pointers (edited code in original question if you want to see), but I am still getting the same errors.
Try auto fut2 = std::async(std::launch::async, &Foo<std::shared_ptr<Bar>>::set_data, &foo2, std::make_shared<Bar>()); , see godbolt.org/z/qzRlxD
2

You just have some typos in your code, in first shared_ptr example you pass shared_ptr<Foo> instead of raw ptr Foo* to your function. In the second example first argument Foo* is correct, but second one is Bar instead of shared_ptr<Bar>. Here you have the working example:

class Bar
{
  public:
    Bar () {}
    double data;
};

template <typename T>
class Foo
{
  public:
    Foo () {}
    T set_data (T d) { data = d; return data; }
  private:
    T data;
};

#include <future>

TEST(xxx, yyy) {
    Foo<std::shared_ptr<Bar> > foo;
    auto bar = std::make_shared<Bar>();
    bar->data = 42;
    auto futureResult = std::async(std::launch::async,
        &Foo<std::shared_ptr<Bar> >::set_data, &foo, bar);
    std::cout << futureResult.get()->data << std::endl;
}

As a side note, if you need to run async operation on some data inside your Foo class, better provide the interface that executes this async operation inside your class and returns the future.

std::future<T> runAsyncOper(const T& data);

1 Comment

Thank you very much for this! I had just found this answer right when you posted it from the previous answer. Thanks for the last note as well that encourages proper encapsulation, but I am not actually implementing my code exactly as the exampled does, so there isn't the same manipulation of class data.
0

Your first example with the shared pointer doesn't pass a shared pointer to std::async as a last parameter, it passes a pointer to function (you didn't add the parentheses)

int main()
{
    /**
    * Works :)
    */
    Foo<std::shared_ptr<Bar>> foo2;

    auto br = std::make_shared<Bar>();

    auto fut2 = std::async(std::launch::async, &Foo<std::shared_ptr<Bar>>::set_data, &foo2, br);

    fut2.wait();
    std::cout << fut2.get()->data << std::endl;

    return 0;
}

Comments

0

Problem is that you passed wrong types of arguments to std::async and used incorrectly std::make_shared.

Minimal modification to your code:

  auto foo2 = std::make_shared<Foo<std::shared_ptr<Bar>>>();
  auto bar = std::make_shared<Bar>();
  auto fut2 = std::async(std::launch::async, &Foo<std::shared_ptr<Bar>>::set_data, foo2, bar);

  fut2.wait();
  std::cout << fut2.get()->data << std::endl;

https://wandbox.org/permlink/3YXG56ahFKrZs8GB

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.