Search code examples
c++boostboost-variantboost-beast

Why does Boost Variant use the template constructor instead of the move constructor for boost::beast::websocket::stream?


I am trying to wrap boost::beast::websocket::stream<T> (for 2 specific T) in a boost::variant to allow treating TLS ([T = boost::asio::ssl::stream<boost::asio::ip::tcp::socket>]) and non-TLS ([T = boost::asio::ip::tcp::socket]) websockets the same. I am stuck at a compilation failure.

The simplest failing example that I can come up with is:

#include <boost/asio/ssl.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/variant.hpp>

using tcp = boost::asio::ip::tcp;
namespace ws = boost::beast::websocket;
namespace ssl = boost::asio::ssl;

using base_ws = boost::variant<
    ws::stream<tcp::socket>, ws::stream<ssl::stream<tcp::socket>>>;

class test
{
 public:
    static void init( tcp::socket &&  socket )
    {
        ssl::context  ctx{ ssl::context::tlsv12_server };
        ws::stream<ssl::stream<tcp::socket>>  s{ std::move( socket ), ctx };
        base_ws{ std::move( s ) };
    }
};

int main()
{}

Which fails to compile with error:

In file included from /server/src/ws_server.cpp:9:
In file included from /include/boost/beast/websocket.hpp:18:
In file included from /include/boost/beast/websocket/stream.hpp:3455:
/include/boost/beast/websocket/impl/stream.ipp:47:7: error: no matching constructor for initialization of 'boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >'
    : stream_(std::forward<Args>(args)...)
      ^       ~~~~~~~~~~~~~~~~~~~~~~~~
/include/boost/variant/detail/initializer.hpp:122:27: note: in instantiation of function template specialization 'boost::beast::websocket::stream<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> > >::stream<boost::beast::websocket::stream<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> > > >' requested here
                new(dest) value_T( boost::detail::variant::move(operand) );
                          ^
/include/boost/variant/variant.hpp:1687:28: note: in instantiation of member function 'boost::detail::variant::make_initializer_node::apply<boost::mpl::pair<boost::detail::variant::make_initializer_node::apply<boost::mpl::pair<boost::detail::variant::initializer_root, mpl_::int_<0> >, boost::mpl::l_iter<boost::mpl::list2<boost::beast::websocket::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >, boost::beast::websocket::stream<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> > > > > >::initializer_node, mpl_::int_<1> >, boost::mpl::l_iter<boost::mpl::list1<boost::beast::websocket::stream<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> > > > > >::initializer_node::initialize' requested here
              initializer::initialize(
                           ^
/include/boost/variant/variant.hpp:1858:9: note: in instantiation of function template specialization 'boost::variant<boost::beast::websocket::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >, boost::beast::websocket::stream<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> > > >::convert_construct<boost::beast::websocket::stream<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> > > >' requested here
        convert_construct( detail::variant::move(operand), 1L);
        ^
/server/src/ws_server.cpp:372:20: note: in instantiation of function template specialization 'boost::variant<boost::beast::websocket::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >, boost::beast::websocket::stream<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> > > >::variant<boost::beast::websocket::stream<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> > > >' requested here
            return base_ws( std::move( s ) );
                   ^
/include/boost/asio/ssl/stream.hpp:64:7: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'boost::beast::websocket::stream<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> > >' to 'const boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >' for 1st argument
class stream :
      ^
/include/boost/asio/ssl/stream.hpp:98:3: note: candidate constructor template not viable: requires 2 arguments, but 1 was provided
  stream(Arg&& arg, context& ctx)
  ^

I would provide godbolt however it times out, and coliru does not provide openssl which is required for boost/asio/ssl.hpp.

Per here, boost::variant should accept T && and use it to instantiate T internally.

Per here, boost::beast::websocket::stream does have a move constructor.

The move constructor is more specific, so why is the compiler selecting the variadic constructor (stream<...>) as indicated in the first note?

boost::beast::websocket::stream<
  boost::asio::ssl::stream<
    boost::asio::basic_stream_socket<boost::asio::ip::tcp>
  >
>::stream<
  boost::beast::websocket::stream<
    boost::asio::ssl::stream<
      boost::asio::basic_stream_socket<boost::asio::ip::tcp>
    >
  >
>

I am using Boost 1.66.0 and Clang 7.


Solution

  • The critical information I overlooked was in the description of boost::beast::websocket::stream::stream(stream&&):

    If NextLayer is move constructible, this function will move-construct a new stream from the existing stream.

    It turns out that boost::asio::ssl::stream is not move constructible, so the defaulted move constructor of stream was not generated. There is an open issue to make ssl::stream move constructible in chriskohlhoff/asio#124 which references the boost bugtracker.

    A solution to the problem then is to use a move constructible implementation of ssl::stream, like the one provided in the Beast examples here, which looks like it may be available at boost/beast/experimental/core/ssl_stream.hpp in a later version of Beast (not in 1.67.0).