0

The following fails (https://godbolt.org/z/qzeqW6eeb):

#include <ppltasks.h>

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

void third_party_lib_call_that_blocks(std::string message) {
  std::cout << message << "\n";
  std::this_thread::sleep_for(std::chrono::seconds(1));
}

class nocopy {
 public:
  explicit nocopy() {}

  nocopy(const nocopy& _Other) = delete;
  nocopy(nocopy&& _Other) = default;
  nocopy& operator=(const nocopy&) = delete;
  nocopy& operator=(nocopy&&) = default;
};

int main() {
  std::string message = "hello";
  nocopy n;

  auto t = concurrency::create_task([message = std::move(message), n = std::move(n)]() {
    std::cout << "start\n";
    third_party_lib_call_that_blocks(message);
    std::cout << "end\n";
  });

  t.wait();

  return 0;
}
C:/data/msvc/14.34.31931-Pre/include\ppltasks.h(4544): error C2338: static_assert failed: 'incorrect argument for create_task; must be either a callable object or a task_completion_event'
<source>(30): note: see reference to function template instantiation 'Concurrency::task<Concurrency::details::_BadArgType> Concurrency::create_task<main::<lambda_1>>(_Ty,Concurrency::task_options)' being compiled
        with
        [
            _Ty=main::<lambda_1>
        ]
Execution build compiler returned: 2

I've narrowed this down to create_task requiring a copy constructor. Is there a way to work around this? I have a move-only object I need to move into this thread.


The move-only object is provided by a separate library. Specifically, the auto&& self parameter provided by asio::async_compose to its lambda, something like:

asio::async_compose<Token, void(std::error_code)>(
        [message = std::move(message)](auto&& self) mutable {
          concurrency::create_task(
              [message = std::move(message), self = std::move(self)]() mutable {
                auto ec = third_party_lib_call_that_blocks(std::move(message));
                self.complete(ec);
              });
        },
        token, executor_);

If anyone knows of a workaround to turn that self into a copyable object, I'd take that too. I have no idea what precise type it is and asio docs don't say.

MHebes
  • 2,290
  • 1
  • 16
  • 29
  • Actually, if I `std::make_shared>(std::move(self))` and put *that* into the task lambda, it gives it a copy constructor. Cludgy but I don't know a better option. – MHebes Jan 20 '23 at 22:38
  • 1
    Yes, this is a limitation of PPL. It requires that the lambda be copyable, because `create_task` makes a copy. The standard workaround which you discovered is to put your non-copyable thing in a `shared_ptr`, and capture the `shared_ptr` (which is copyable). – Raymond Chen Jan 20 '23 at 23:21
  • @RaymondChen makes sense. Does that mean `std::async` has the same restrictions since it’s implemented with `create_task`? `std::thread` seems happy enough with move-only callables – MHebes Jan 22 '23 at 23:51
  • 1
    You can see [how `std::async` works around that problem](https://github.com/microsoft/STL/blob/40e90706d9ca35865200b972964644933c4e4737/stl/inc/future#L1419): It moves the callable onto the heap so the thing that gets moved is the pointer, not the callable. – Raymond Chen Jan 23 '23 at 18:41

0 Answers0