user8922003
user8922003

Reputation:

C++ boost::asio Variadic template operator in accept handler for boost::asio::basic_socket_acceptor::async_accept()

I'm trying to create an asynchronous tcp server which accepts connection. I'm about to use an accept handler function object which looks like the following:

template <typename Function>
struct callback
{
   Function func_;

   callback()
   {
   }

   callback(Function&& f)
        : func_(std::forward<Function>(f))
   {
   }

   template <typename ...Args>
   void operator() (Args&& ...args)
   {
         func_(std::forward<Args>(args)...);
   }
};

My server class:

#include <boost/bind.hpp>
#include <boost/asio.hpp>

#include <session.hpp>
#include <handler.hpp>
class server
{
private:
    typedef callback<boost::function<void(const boost::system::error_code&, session_ptr&)>> accept_handler;

    boost::asio::io_service& io_service_;
    tcp::acceptor acceptor_;
    accept_handler handler_;

public:
    server(boost::asio::io_service& io_service, short port)
        : io_service_(io_service),
          acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
    {
        session_ptr new_session = session_ptr(new session(io_service_));
        auto callback =  boost::bind(&server::handle_accept,
                                     this,
                                     boost::asio::placeholders::error,
                                     new_session);

        handler_ = accept_handler(std::move(callback));


        acceptor_.async_accept(new_session->socket(),
                               handler_);
    }

    void handle_accept(const boost::system::error_code& error, session_ptr new_session)
    {
        if (!error)
        {
            new_session->start();
            new_session.reset(new session(io_service_));

            acceptor_.async_accept(new_session->socket(),
                                   handler_);

        }
    }
};

But when I try to compile I get following error:

error: no match for call to ‘(boost::function&)>) (const boost::system::error_code&)’ func_(std::forward(args)...);

So I must only use handler which meet AcceptHandler requirements

struct accept_handler
{
  ...
  void operator()(
      const boost::system::error_code& ec)
  {
    ...
  }
  ...
};

or there is a solution to use overloaded variadic template opreator() ?

Upvotes: 2

Views: 300

Answers (1)

sehe
sehe

Reputation: 393769

UPDATED After realizing the real mistake:

The good news is: the error is quite simply fixed by changing one line:

typedef callback<boost::function<void(const boost::system::error_code&, session_ptr&)>> accept_handler;

into

typedef callback<boost::function<void(const boost::system::error_code&)>> accept_handler;

The previous definition was simply wrong for all reasons:

  • it doesn't fit the handler requirements
  • it also doesn't match the bind expression: bind(&server::handle_accept, this, asio::placeholders::error, new_session). Note it has only 1 placeholder (asio::placeholders::error) so it couldn't possibly work with 2 parameters

Live On Coliru

#include <boost/function.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>

namespace ba = boost::asio;
using ba::ip::tcp;

struct session : std::enable_shared_from_this<session> {
    session(ba::io_service& svc) : _socket(svc) {}

    void start() {} // do something asynchronously
    tcp::socket& socket() { return _socket; }

    tcp::socket _socket;
};

using session_ptr = std::shared_ptr<session>;

template <typename Function>
struct callback
{
   Function func_;

   callback(Function&& f = {}) : func_(std::forward<Function>(f)) {
   }

   template <typename ...Args>
   void operator() (Args&& ...args) {
       func_(std::forward<Args>(args)...);
   }
};

class server
{
private:
    typedef callback<boost::function<void(const boost::system::error_code&)>> accept_handler;

    ba::io_service& io_service_;
    tcp::acceptor acceptor_;
    accept_handler handler_;

public:
    server(ba::io_service& io_service, short port)
        : io_service_(io_service),
          acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
    {
        session_ptr new_session = session_ptr(new session(io_service_));
        handler_ = accept_handler(bind(&server::handle_accept, this, ba::placeholders::error, new_session));

        acceptor_.async_accept(new_session->socket(), handler_);
    }

    void handle_accept(const boost::system::error_code& error, session_ptr new_session)
    {
        if (!error)
        {
            new_session->start();
            new_session.reset(new session(io_service_));

            acceptor_.async_accept(new_session->socket(),
                                   handler_);

        }
    }
};

int main() {
}

Simplify: Remove Redundant Wrappers

The only thing I'd note here is that quite clearly, none callback<>, accept_handler have any use:

Live On Coliru

class server
{
private:
    ba::io_service& io_service_;
    tcp::acceptor acceptor_;

public:
    server(ba::io_service& io_service, short port)
        : io_service_(io_service),
          acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
    { 
        do_accept();
    }

    void do_accept() {
        session_ptr new_session = session_ptr(new session(io_service_));
        acceptor_.async_accept(new_session->socket(), bind(&server::handle_accept, this, ba::placeholders::error, new_session));
    }

    void handle_accept(const boost::system::error_code& error, session_ptr new_session)
    {
        if (!error) {
            new_session->start();
            do_accept();
        }
    }
};

Even Simpler: Capturing Lambda

You can do without the bind, the placeholder, the handle_accept member all at the same time:

Live On Coliru

class server
{
private:
    ba::io_service& io_service_;
    tcp::acceptor acceptor_;

public:
    server(ba::io_service& io_service, short port)
        : io_service_(io_service),
          acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
    { 
        do_accept();
    }

    void do_accept() {
        session_ptr new_session = std::make_shared<session>(io_service_);

        acceptor_.async_accept(new_session->socket(), [this,new_session](const boost::system::error_code& error) {
            if (!error) {
                new_session->start();
                do_accept();
            }
        });
    }
};

Old Answer

In the first reading I had assumed you ran into a classic pitfall I frequently run into with Boost Asio and polymorphic lambdas. Sorry.

Indeed, in the variadic case the concept check cannot verify the handler requirements. My approach here would be to disable the requirement checks:

#define BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS 1

The main thing lost is friendlier error messages if there is a mismatch:

Asio 1.6.0 / Boost 1.47

  • Added friendlier compiler errors for when a completion handler does not meet the necessary type requirements. When C++0x is available (currently supported for g++ 4.5 or later, and MSVC 10), static_assert is also used to generate an informative error message. This checking may be disabled by defining BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS.

See http://www.boost.org/doc/libs/1_65_1/doc/html/boost_asio/history.html

Upvotes: 1

Related Questions