Riot
Riot

Reputation: 16746

boost::bind with member functions (as boost::asio async write handler)

I'm using boost::bind to pass a handler function to boost::asio::async_write. When I use free functions, it works fine, but when I try to move the functions inside a class, bind produces errors that I am unable to decipher.

What works:

I write some data with:

boost::asio::async_write(*socket,
                         boost::asio::buffer(data(),
                                             length()),
                         boost::bind(handlermessagewrite,
                                     boost::asio::placeholders::error,
                                     this,
                                     boost::asio::placeholders::bytes_transferred));

Then I handle the write with a free function whose signature is:

void handlermessagewrite(const boost::system::error_code& errorcode,
                         iodata *msg,
                         size_t bytes_transferred);

This all works as expected.

What I'm trying to do:

I'm moving the handler inside a class ioclient:

class ioclient {
public:
  void handlermessagewrite(const boost::system::error_code& errorcode,
                           iodata *msg,
                           size_t bytes_transferred);
}

void ioclient::handlermessagewrite(const boost::system::error_code& errorcode,
                                   iodata *msg,
                                   size_t bytes_transferred);

and adapting the boost::bind code accordingly, as seen in the official asio tutorials:

- boost::bind(handlermessagewrite,

+ boost::bind(&ioclient::handlermessagewrite,

However, this produces some extremely opaque compile errors, not helped by the fact that one of the lines seems to end up truncated in my IDE (code::blocks):

\boost\bind\bind_template.hpp|102| required from 'boost::_bi::bind_t::result_type boost::_bi::bind_t::operator()(const A1&, const A2&) [with A1 = boost::system::error_code; A2 = unsigned int; R = void; F = boost::_mfi::mf2; L = boost::_bi::list3 (*)(), boost::_bi::value, boost::arg<2> (*)()>; boost::_bi::bind_t::result_type = void]'| \boost\asio\impl\write.hpp|261| required from 'void boost::asio::detail::write_op::operator()(const boost::system::error_code&, std::size_t, int) [with AsyncWriteStream = boost::asio::basic_stream_socket; CompletionCondition = boost::asio::detail::transfer_all_t; WriteHandler = boost::_bi::bind_t, boost::_bi::list3 (*)(), boost::_bi::va| \boost\asio\impl\write.hpp|585| required from 'void boost::asio::async_write(AsyncWriteStream&, const ConstBufferSequence&, WriteHandler&&) [with AsyncWriteStream = boost::asio::basic_stream_socket; ConstBufferSequence = boost::asio::mutable_buffers_1; WriteHandler = boost::_bi::bind_t, boost::_bi::list3 (*)(), boost::_bi::value, boost::arg<2> (*)()> >]'| \iodata.cpp|76| required from here| \boost\bind\bind.hpp|392|error: no match for call to '(boost::_mfi::mf2) (const boost::system::error_code&, iodata*&, const unsigned int&)'| \boost\bind\mem_fn_template.hpp|253|note: candidates are:| \boost\bind\mem_fn_template.hpp|278|note: R boost::_mfi::mf2::operator()(T*, A1, A2) const [with R = void; T = ioclient; A1 = const boost::system::error_code&; A2 = iodata*]| \boost\bind\mem_fn_template.hpp|278|note: no known conversion for argument 1 from 'const boost::system::error_code' to 'ioclient*'| \boost\bind\mem_fn_template.hpp|283|note: template R boost::_mfi::mf2::operator()(U&, A1, A2) const [with U = U; R = void; T = ioclient; A1 = const boost::system::error_code&; A2 = iodata*]| \boost\bind\mem_fn_template.hpp|283|note: template argument deduction/substitution failed:| \boost\bind\bind.hpp|392|note: cannot convert '(& a)->boost::_bi::list2::operator[]((* &((boost::_bi::list3 (*)(), boost::_bi::value, boost::arg<2> ()()>)this)->boost::_bi::list3 (*)(), boost::_bi::value, boost::arg<2> (*)()>::.boost::_bi::storage3 (*)(), boost::_bi::value, boost::arg<2> (*)()>::.boost::_bi::storage2 (*)(), boost::bi::value >::a2))' (type 'iodata*') to type 'const boost::system::| \boost\bind\mem_fn_template.hpp|291|note: template R boost::_mfi::mf2::operator()(const U&, A1, A2) const [with U = U; R = void; T = ioclient; A1 = const boost::system::error_code&; A2 = iodata*]| \boost\bind\mem_fn_template.hpp|291|note: template argument deduction/substitution failed:| \boost\bind\bind.hpp|392|note: cannot convert '(& a)->boost::_bi::list2::operator[]((* &((boost::_bi::list3 (*)(), boost::_bi::value, boost::arg<2> ()()>)this)->boost::_bi::list3 (*)(), boost::_bi::value, boost::arg<2> (*)()>::.boost::_bi::storage3 (*)(), boost::_bi::value, boost::arg<2> (*)()>::.boost::_bi::storage2 (*)(), boost::bi::value >::a2))' (type 'iodata*') to type 'const boost::system::| \boost\bind\mem_fn_template.hpp|299|note: R boost::_mfi::mf2::operator()(T&, A1, A2) const [with R = void; T = ioclient; A1 = const boost::system::error_code&; A2 = iodata*]| \boost\bind\mem_fn_template.hpp|299|note: no known conversion for argument 1 from 'const boost::system::error_code' to 'ioclient&'|

I'm convinced I'm doing something wrong with bind, but I'm at a loss as to what that may be. Any ideas?

Upvotes: 2

Views: 5893

Answers (2)

Tanner Sansbury
Tanner Sansbury

Reputation: 51961

The initial example is failing because the object instance on which the member-function will be invoked is not passed to bind. This is indicated in the compiler error where it states that there is no known conversion from const boost::system::error_code to ioclient*.

When using Boost.Bind with member pointers, it is documented that

boost::bind(&X::f, args) is equivalent to boost::bind<R>(boost::mem_fn(&X::f), args).

Furthermore, Boost.mem_fn's documentation states:

It [boost::mem_fn] supports member function pointers with more than one argument, and the returned function object can take a pointer, a reference, or a smart pointer to an object instance as its first argument.

Thus, if this first argument of boost::bind is a member-pointer, then either:

  • The second argument in the bind call must be a handle to the object instance, as it will be the first argument passed to the function object returned from boost::mem_fn.
  • The function object returned from boost::bind must be passed the object instance handle in the argument position matching the _1 placeholder.

For example, given

struct Foo
{
  void do_something(int x) {}
};

The do_something member function could be bound and invoked with any of the following:

Foo f;
boost::bind(&Foo::do_something, &f, _1)(42);     // handle is second argument.
boost::bind(&Foo::do_something, _1, _2)(&f, 42); // handle matches _1 position.
boost::bind(&Foo::do_something, _2, _1)(42, &f); // handle matches _1 position.

With Boost.Asio, boost::asio::placeholders::error is implemented as placeholder _1. For this reason, the object instance must be passed to the bind call as the second argument, or the object instance must be passed as an argument to either a free function or static member function, that will then invoke the member function on the object instance. Here is an example of solution that compiles with a non-static member function, but the snippet of interests is:

ioclient client;
iodata data;

boost::asio::async_write(
  socket,
  boost::asio::null_buffers(),
  boost::bind(&ioclient::handlermessagewrite,
              &client,
              boost::asio::placeholders::error,
              &data,
              boost::asio::placeholders::bytes_transferred));

The compiler error posted in the response to Lou indicates that an ioclient member function is attempting to be invoked with an instance handle of iodata, an incompatible type. For iodata to be a compatible type, it must be inherited from ioclient. If this is the intended type hierarchy, then verify that inheritance is correct. Otherwise, carefully match the argument types and positions with the function being bound.

Upvotes: 3

Lou
Lou

Reputation: 1955

When using a instance method, you must pass the this pointer as the second argument to bind().

EDIT:

I've done what you are trying to do with boost::asio. Here is a snippet from my implementation:

        boost::asio::async_write(
            m_Socket,
            boost::asio::buffer((const unsigned char *)(rMsg.c_str()), rMsg.length()),
            boost::bind(&ServerToClientConnT::HandleAsioWrite,
                        this,
                        boost::asio::placeholders::error,
                        boost::asio::placeholders::bytes_transferred));

where HandleAsioWrite is method declared as follows:

void HandleAsioWrite(const boost::system::error_code& rErrorCode,
                     std::size_t nBytesTransferred);

Upvotes: 4

Related Questions