5

I am using asio standalone 1.10.6 and vs2015 rc.

The vs2015 support unique_ptr capture. So I wrote some code looks like:

auto data = std::make_unique<std::string>("abc");
auto buffer = asio::buffer(data->c_str(), data->size());
asio::async_write(s, buffer, [data = std::move(data)](
  const asio::error_code& error, size_t byte_transferred) mutable {
  do_something(std::move(data), error, byte_transferred);
});

But when I compile the code, the compiler said:

error C2280: .... attempting to reference a deleted function

As my understand, it said that I try to copy the lambda, and because the lambda capture a std::unique_ptr, so it is noncopyable.

What make me confused is why the asio want to copy the lambda but not move the lambda.

What's wrong with my code? How to workaround it?

=========================

The full code is:

void do_something(std::unique_ptr<std::string> data) { }

void compile_failed() {
  asio::io_service io_service;
  asio::ip::tcp::socket s(io_service);

  auto data = std::make_unique<std::string>("abc");
  auto buffer = asio::buffer(data->c_str(), data->size());

  asio::async_write(s, buffer, [data = std::move(data)](const asio::error_code& error,
    size_t byte_transferred) mutable {
    do_something(std::move(data));
  });
}

template<typename T > struct lambda_evil_wrap {
  mutable T ptr_;
  lambda_evil_wrap(T&& ptr) : ptr_(std::forward< T>(ptr)) {}
  lambda_evil_wrap(lambda_evil_wrap const& other) : ptr_(std::move(other.ptr_)) {}
  lambda_evil_wrap & operator=(lambda_evil_wrap& other) = delete;
};

void compile_success_but_very_danger() {
  asio::io_service io_service;
  asio::ip::tcp::socket s(io_service);

  auto data = std::make_unique<std::string>("abc");
  auto buffer = asio::buffer(data->c_str(), data->size());

  lambda_evil_wrap<std::unique_ptr<std::string>> wrapper(std::move(data));
  asio::async_write(s, buffer, [wrapper](const asio::error_code& error,
    size_t byte_transferred) mutable {
    do_something(std::move(wrapper.ptr_));
  });
}

int _tmain(int argc, _TCHAR* argv[])
{
    return 0;
}

As the code, if I wrapped the unique_ptr to a copyable object, compile is OK. But the lambda_evil_wrap::lambda_evil_wrap(lambda_evil_wrap const& a) is really suck and unsafe. I do not know if asio author wrote some code looks like:

Handler handler2(handler);
handler(...); // Crash here
alpha
  • 1,228
  • 1
  • 11
  • 26
  • `std::ref(lambda)` ? – Jarod42 Jun 11 '15 at 11:44
  • Are you sure it's that's the problem? Do the compiler say anything else? Always include the complete and unedited error log in the question. – Some programmer dude Jun 11 '15 at 11:45
  • Of course the std::ref(lambda) can make compiler happy. But the code is wrong. When the function return, the data will be freed and crash in do_something. – alpha Jun 11 '15 at 11:57
  • Joachim Pileborg: I am sure it's because the asio want the handler copyable. The following code works: std::shared_ptr data2(std::move(data)); asio::async_write(... [data2](...){}); – alpha Jun 11 '15 at 12:06
  • It's weird that only io_context::post require the 'move assignment', but something else like asio::async_connect or steady_timer::async_wait can accept 'move constructor'. – vrqq Jun 02 '21 at 15:16

1 Answers1

7

The error in the original code is that the handler fails to meet the Handler type requirement as it is not CopyConstructible:

A handler must meet the requirements of CopyConstructible types (C++ Std, 20.1.3).

As noted in Boost.Asio's C++11 support for movable handlers, where possible, Boost.Asio will prefer a move constructor over a copy constructor, but the handler must still be copy constructible:

[...] Boost.Asio's implementation will use a handler's move constructor in preference to its copy constructor. In certain circumstances, Boost.Asio may be able to eliminate all calls to a handler's copy constructor. However, handler types are still required to be copy constructible.

To resolve the issue, one may consider using std::shared_ptr instead of std::unique_ptr, causing the lambda to be copy constructible.

Tanner Sansbury
  • 51,153
  • 9
  • 112
  • 169
  • I known the shared_ptr would work. But the do_something(...) want a unique_ptr and I can not convert the shared_ptr to unique_ptr even if the shared_ptr<>.unique() is true. So, how to workaround it? – alpha Jun 11 '15 at 15:25
  • @alpha One could manage the the type an API needs (`std::unique_ptr`) with a copyable type (`std::shared_ptr>`). While the extra level of indirection may not be ideal, it still provides safe semantics. See [this](http://coliru.stacked-crooked.com/a/f012b9dbc2fa7e89) for a demonstration. – Tanner Sansbury Jun 11 '15 at 17:34
  • Nice. I just read your code. It's a good workaround solution. Much better than my lambda_evil_wrap. – alpha Jun 12 '15 at 02:00
  • I still have some questions: Since the C++ Std requires that the handler type must be copyable, why the C++14 add feature of the lambda move capture? In c++ 11, we use lambda_evil_wrap or wrap the unique_ptr into shared_ptr just like your code, in c++ 14, we still need to use those wrapper solution. – alpha Jun 12 '15 at 02:30
  • 1
    @alpha Boost.Asio defines the requirement for its Handlers to be CopyConstructible, not the standard. The C++ concept of CopyConstructible is defined by the standard. – Tanner Sansbury Jun 12 '15 at 02:42