2
votes

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?

2
Why don't you take a look at bind documentation? boost.org/doc/libs/1_52_0/libs/bind/…Igor R.
Of course I've looked through the bind documentation, as well as other sources and every relevant thread here too. I found blog.think-async.com/2010/04/bind-illustrated.html a very helpful source of info for bind in general, and hopefully that can help others too - however, that doesn't solve my problem.Riot
You can't swap arguments like this. The first argument of the resulting functor should be error_code, 2nd size_t - these are the 2 parameters, which are passed by the caller (asio). If your function accepts more parameters, they should be bound in bind expression.Igor R.
Igor: thank you, but I'm afraid you're wrong. It is possible to have arguments arranged the way I have them, and as i stated in the question, the first formulation I laid out works perfectly. Here is a diagram demonstrating how: think-async.com/blog/bind-illustrated/… The problem I am having is when I attempt to change the free function to one that belongs to a class. If the argument order was a problem, it wouldn't work with a free function either, but it does.Riot
While there is a lot of noise in the error message, the important line is " no known conversion for argument 1 from 'const boost::system::error_code' to 'ioclient*" As Igor recommended, I would rewrite it in a straight-forward way and get it compiling first before trying argument re-ordering, etc. Also, as Lou stated, when you bind to a member function, the this pointer needs to be the argument. Also, if this does result in another compiler error message, you need to post that message here so that others can help you further.Ralf

2 Answers

4
votes

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);
3
votes

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.