Search code examples
c++sslboostboost-asiosslstream

Can't compile code with two ssl_stream that wrap different next layers


I want to understand why when creating two ssl_streams wrapping different objects for the tcp layer, I get the following compilation errors:

------ Build started: Project: test, Configuration: Debug x64 ------
>Microsoft (R) C/C++ Optimizing Compiler Version 19.22.27905 for x64
>Copyright (C) Microsoft Corporation.  All rights reserved.
>cl /c /I"D:\workspace\openssl-static\openssl-OpenSSL_1_1_1a\openssl-OpenSSL_1_1_1a\include" /ID:\workspace\boost\VS19\boost_1_70_0 /Zi /W3 /WX- /diagnostics:column /Od /Ob0 /D WIN32 /D _WINDOWS /D _WIN32_WINNT=0x0600 /D _SILENCE_CXX17_ALLOCATOR_VOID_DEPRECATION_WARNING /D "CMAKE_INTDIR=\"Debug\"" /D _MBCS /Gm- /EHsc /RTC1 /MTd /GS /fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /GR /std:c++17 /Fo"test.dir\Debug\\" /Fd"test.dir\Debug\vc142.pdb" /Gd /TP /errorReport:prompt D:\workspace\main.cpp
>main.cpp
>D:\workspace\boost\VS19\boost_1_70_0\boost/beast/ssl/ssl_stream.hpp(655,5): error C2995:  'void boost::beast::teardown(boost::beast::role_type,boost::beast::ssl_stream<NextLayer> &,boost::system::error_code &)': function template has already been defined
>D:\workspace\boost\VS19\boost_1_70_0\boost/beast/ssl/ssl_stream.hpp(655): message :  see declaration of 'boost::beast::teardown'
>D:\workspace\main.cpp(12): message :  see reference to class template instantiation 'boost::beast::ssl_stream<boost::asio::ip::tcp::socket>' being compiled
>D:\workspace\boost\VS19\boost_1_70_0\boost/beast/ssl/ssl_stream.hpp(668,5): error C2995:  'void boost::beast::async_teardown(boost::beast::role_type,boost::beast::ssl_stream<NextLayer> &,TeardownHandler &&)': function template has already been defined
>D:\workspace\boost\VS19\boost_1_70_0\boost/beast/ssl/ssl_stream.hpp(668): message :  see declaration of 'boost::beast::async_teardown'
>Done building project "test.vcxproj" -- FAILED.

All errors disappear if both streams have the same template parameter (that is, either both have a boost::beast::tcp_stream or boost::asio::ip::tcp::socket).

#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/beast/core/tcp_stream.hpp>
#include <boost/beast/ssl/ssl_stream.hpp>

int main() {
  boost::asio::io_context ioc;
  boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23_client);
  ctx.set_default_verify_paths();
  boost::beast::ssl_stream<boost::beast::tcp_stream> stream_1(ioc, ctx);
  boost::beast::ssl_stream<boost::asio::ip::tcp::socket> stream_2(ioc, ctx);
}

I want to understand if this is a bug, or desired behavior. If the second, it would be great if someone explained to me why. Also, I would like to better understand in what cases it's worth using the tcp_stream, and in which the socket.


Solution

  • I think It's a bug/QOI issue in Beast. It declares template friend functions inside the class declaration, i.e. teardown.

    But the template friends don't depend on all the class template template arguments so the next instantation of ssl_stream<> with other template arguments defines the same template friend function.

    The simple fix would be to make the definition out-of-class and the friend declaration only in the class body.

    So, it becomes:

    template<class NextLayer>
    class ssl_stream
        : public net::ssl::stream_base
    {
        // ... many lines snipped ...
    
        #if ! BOOST_BEAST_DOXYGEN
        template<class SyncStream>
        friend
        void
        teardown(
            boost::beast::role_type role,
            ssl_stream<SyncStream>& stream,
            boost::system::error_code& ec);
    
        template<class AsyncStream, class TeardownHandler>
        friend
        void
        async_teardown(
            boost::beast::role_type role,
            ssl_stream<AsyncStream>& stream,
            TeardownHandler&& handler);
        #endif
    };
    
    template<class SyncStream>
    static inline void
    teardown(
        boost::beast::role_type role,
        ssl_stream<SyncStream>& stream,
        boost::system::error_code& ec)
    {
        // Just forward it to the underlying ssl::stream
        using boost::beast::websocket::teardown;
        teardown(role, *stream.p_, ec);
    }
    
    template<class AsyncStream, class TeardownHandler>
    static inline void
    async_teardown(
        boost::beast::role_type role,
        ssl_stream<AsyncStream>& stream,
        TeardownHandler&& handler)
    {
        // Just forward it to the underlying ssl::stream
        using boost::beast::websocket::async_teardown;
        async_teardown(role, *stream.p_,
            std::forward<TeardownHandler>(handler));
    }
    

    I've opened an issue + pull request for that

    I was going to open a pull request but it appears that the identical fix (yay) already landed on develop in 1de60a046292dcd42bb0097176e0139ba4ad051b which is in master and tagged for boost-1.71.0 and boost-1.71.0.beta1