2

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?

Riot
  • 15,723
  • 4
  • 60
  • 67
  • Why don't you take a look at `bind` documentation? http://www.boost.org/doc/libs/1_52_0/libs/bind/bind.html#with_member_pointers – Igor R. Dec 08 '12 at 07:47
  • 1
    Of course I've looked through the bind documentation, as well as other sources and every relevant thread here too. I found http://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 Dec 08 '12 at 08:05
  • 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. Dec 08 '12 at 08:31
  • i.e. your function should look like this: void `ioclient::handlermessagewrite(const boost::system::error_code& errorcode, size_t bytes_transferred, YourArg1 arg1, YourArg2 arg2);` And the bind should be: `bind(&ioclient::handlermessagewrite, this, _1, _2, yourParam1, yourParam2);` – Igor R. Dec 08 '12 at 08:34
  • 1
    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: http://think-async.com/blog/bind-illustrated/bind-plain-function-one-var-two-args-reordered.png 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 Dec 08 '12 at 10:05
  • well, while this "advanced" way of binding definitely exists, I'm afraid it's quite error prone for you, that's why I'd recommend to write it fist in a simple and straightforward way, so you'd be sure that the arguments order and types match. In particular, you pass `this` for `iodata`, but OTOH you pass `this` for `ioclient`... – Igor R. Dec 08 '12 at 12:49
  • 1
    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 Dec 08 '12 at 19:39

2 Answers2

4

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);
Lou
  • 1,955
  • 14
  • 16
  • As it happens I'm passing the `this` pointer to a separate argument of my handler, not as the session/context for the bound function - that's why it's not the second argument there. If i introduce an additional `this` pointer in the second position, and either keep or remove my `this` argument, the compile still fails with the following errors: http://pastebin.com/tFKUncm2 – Riot Dec 08 '12 at 08:14
  • 1
    @Riot I also have trouble interpreting the template invokation errors. If you are passing the this ptr as a separate argument, then make the method static and that should work as well. Can you update the question with the complete bind call you are trying to get working. If you've tried two different ways, show both please. – Lou Dec 08 '12 at 17:54
3

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.

Community
  • 1
  • 1
Tanner Sansbury
  • 51,153
  • 9
  • 112
  • 169