Search code examples
c++booststdbindboost-beast

Cannot store bind_front_handler return value in variable


With these present in other parts of my codebase,

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

boost::asio::io_context& io_context_;
tcp::acceptor acceptor_;
void server::on_accept(boost::beast::error_code ec, boost::asio::ip::tcp::socket socket);

I have noticed that this piece of code compiles:

auto strand = net::make_strand(io_context_);
std::shared_ptr<server> this_pointer = shared_from_this();

acceptor_.async_accept(
    strand,
    boost::beast::bind_front_handler(&server::on_accept, this_pointer)
);

whereas this does not:

auto strand = net::make_strand(io_context_);
std::shared_ptr<server> this_pointer = shared_from_this();

auto call_next = boost::beast::bind_front_handler(&server::on_accept, this_pointer);

acceptor_.async_accept(
    strand,
    call_next
);

and it fails with the error

/usr/include/boost/beast/core/detail/bind_handler.hpp:251:45: error: cannot convert ‘boost::beast::detail::bind_front_wrapper<void (server::*)(boost::system::error_code, boost::asio::basic_stream_socket<boost::asio::ip::tcp>), std::shared_ptr<server> >’ to ‘void (server::*)(boost::system::error_code, boost::asio::basic_stream_socket<boost::asio::ip::tcp>)’ in initialization
  251 |         , args_(std::forward<Args_>(args)...)

I am very curious why passing the value returned from bind_front_handler directly to the async_accept would work but storing that value in a variable and then passing that variable would not work.

I also understand very little about Boost and Beast right now, but here it appears to me like I am forgetting something very basic about C++ itself. Why are both of those piece of code not equivalent?


Solution

  • Indeed, you should not be doing that. The bind-front wrapper wants to be a temporary (in that it is move only). You could "fix" it by doing

        acceptor_.async_accept(strand, std::move(call_next));
    

    (after which you will have to remember that call_next may not be used again because it has been moved-from).

    I would personally go the other way - as this helper was clearly intended - and write the idiomatic

        acceptor_.async_accept(
            make_strand(io_context_),
            bind_front_handler(&server::on_accept, shared_from_this()));
    

    Which replaces the entire function.

    Demo

    Live On Coliru

    #include <boost/beast.hpp>
    #include <boost/asio.hpp>
    #include <iostream>
    namespace net = boost::asio;
    namespace beast = boost::beast;
    using boost::system::error_code;
    using net::ip::tcp;
    
    struct server : std::enable_shared_from_this<server> {
        server() {
            acceptor_.listen();
        }
    
        void start(){
            using beast::bind_front_handler;
            acceptor_.async_accept(
                make_strand(io_context_),
                bind_front_handler(&server::on_accept, shared_from_this()));
        }
    
        void wait() {
            work_.reset();
    
            if (thread_.joinable())
                thread_.join();
        }
    
      private:
        void on_accept(error_code ec, tcp::socket&& socket) {
            std::cout << "Accepted connection from " << socket.remote_endpoint() << "\n";
            //// loop to accept more:
            // start();
        }
    
        net::io_context io_context_;
        tcp::acceptor   acceptor_{io_context_, {{}, 9999}};
    
        net::executor_work_guard<net::io_context::executor_type> work_{
            io_context_.get_executor()};
        std::thread thread_{[this] { io_context_.run(); }};
    };
    
    int main()
    {
        auto s = std::make_shared<server>();
        s->start();
        s->wait();
    }
    

    With

    g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp
    ./a.out& sleep .5; nc 127.0.0.1 9999 <<<'hello world'; wait
    

    Prints e.g.

    Accepted connection from 127.0.0.1:36402