0

I'm realizing a simple server backend which can work with tcp and unix sockets.
Here is the listener block:

static asio::awaitable<void> listener() {
    const auto executor = co_await asio::this_coro::executor;
    asio::local::stream_protocol::acceptor acceptor(executor, asio::local::stream_protocol::endpoint(socket_file_));

    for (;;) {
        auto socket = co_await acceptor.async_accept(asio::use_awaitable);
        ...

After that the session is initializing by creating an instance of Session class with this socket.

In this case socket has type asio::local::stream_protocol::socket, but in tcp case the type will be asio::ip::tcp::socket.
All of them has one base class - asio::basic_stream_socket and I want to realize generic Session class:

template <typename Protocol, typename Executor>
class Session : public std::enable_shared_from_this<Session<Protocol, Executor>> {
    using Socket = asio::basic_stream_socket<Protocol, Executor>;

public:
    explicit Session(Socket sock) : socket_(std::move(sock)) {}

    void start() {
        co_spawn(socket_.get_executor(),
                 [self = this->shared_from_this()] { return self->reader(); },
                 asio::detached);
    }

private:
    Socket socket_;

    asio::awaitable<void> reader() {
        ...

In this realization I must to explicitly specify types Protocol and Executor in listener method:

    std::make_shared<Session<asio::local::stream_protocol, asio::any_io_executor>>(std::move(socket))->start();

or

    std::make_shared<Session<decltype(socket)::protocol_type, decltype(socket)::executor_type>>(std::move(socket))->start();

what looks cumbersome and seems redundant.

Is there any way to optimize the Session class template definition?

Kosterio
  • 109
  • 1
  • 7
  • Why is Session derived from `std::enable_shared_from_this`? It doesn't seem relevant to your question or am I missing something? – joergbrech Mar 29 '23 at 12:47
  • @joergbrech It's very relevant as it is the dominant pattern to achieve lifetime management for asynchronous sessions in Asio – sehe Mar 29 '23 at 13:26
  • Of course we don't see the code, but does your Session care about the protocol? I'd consider just templating on the concrete parameters instead of indirectly via template arguments. I think what you want to do can be done, but will lead to unnecessarily complicated templates. I remember doing a similar answer for UPD/TCP earlier will find a link. Found it https://stackoverflow.com/questions/71992182/is-there-a-problem-with-the-socket-i-wrote-for-boostsocket-for-the-simplifyin/71993476#71993476 – sehe Mar 29 '23 at 13:28
  • @sehe, yes, I do not care about the protocol. But in your answer I did not find the difference with my solution, if I'm not mistaken. Is it possible to use ```concept```/```requires``` for this case? – Kosterio Mar 29 '23 at 14:59
  • @Kosterio I'd template more like `template class Session : public std::enable_shared_from_this> {` then. If you need help figuring out details from there - I'm happy to help if you post more self-contained code (to the edited comment: yes, I suppose you would be happy with the `AsyncReadStream` and `AsyncWriteStream` concepts) – sehe Mar 29 '23 at 15:02
  • @sehe yes, just ```typename Socket``` is working solution, but I would like to specify that this is ```basic_stream_socket```, with any proto and executor. – Kosterio Mar 29 '23 at 15:23
  • I think you could - and should - just specify the required concept. Which is `AsyncStream` (that's a Beast name for both `AsyncReadStream` and `AsyncWriteStream`). You don't need to know how it's implemented. In fact, that would just prevent you from writing tests or e.g. using it with files, pipes, SSL streams, serial ports etc. – sehe Mar 29 '23 at 17:06
  • Besides the ```socket_.get_executor()``` call I will use ```async_read_some``` and ```async_write``` methods in awaitable ```reader``` method. All of it will work and with only ``````, but I assumed that in c++20 I could specify what is the base class of typename in my template, even if it templated also. – Kosterio Mar 29 '23 at 22:40

1 Answers1

1

As a disclaimer, I know nothing about asynchronous sessions with boost asio. But if I understand your question correctly, you want to simplify the creation of shared_ptrs to your Session class without having to write out complicated template parameters.

In general, you can achieve this with a templated free factory function create_session, that can deduce its template parameters from the passed argument like so:

#include <memory>

//********************** my very own boost asio implementation **********************//

template <typename Protocol, typename Executor>
struct asio_basic_stream_socket{
    using protocol_type = Protocol;
    using executor_type = Executor;
};

template <typename Protocol, typename Executor>
struct asio_local_stream_protocol_socket : asio_basic_stream_socket<Protocol, Executor> {};

template <typename Protocol, typename Executor>
struct asio_ip_tcp_socket : asio_basic_stream_socket<Protocol, Executor> {};

//********************** Session **********************//

template <typename Protocol, typename Executor>
class Session : public std::enable_shared_from_this<Session<Protocol, Executor>> 
{
public:
    using Socket = asio_basic_stream_socket<Protocol, Executor>;

    template <typename S>
    friend auto create_session(S);
private:
    Session(Socket){}
};

template <typename Socket>
auto create_session(Socket s)
{
    using P = typename Socket::protocol_type;
    using E = typename Socket::executor_type;
    using S = Session<P,E>;
    return std::shared_ptr<S>(new S(std::move(s)));
}

//********************** Usage **********************//

int main()
{
    asio_local_stream_protocol_socket<int, bool> x;
    auto session_ptr_x = create_session(x);

    asio_ip_tcp_socket<std::string, double> y;
    auto session_ptr_y = create_session(x);
}

Some notes:

  • You still have a bit of template mess in the body of create_session, but this is hidden away nicely.
  • At least for the sake of this minimal example, Session does not need to be derived from std::enable_shared_from_this.
  • In the example, the constructor of Session is private. This is based on the assumption that Session instances will only ever be created via the factory function. This is not a requirement of course.

https://godbolt.org/z/qExz4WzKn

joergbrech
  • 2,056
  • 1
  • 5
  • 17
  • The whole point of `shared_from_this` is that the implementation of the class can get a shared pointer to itself from `this`. – sehe Mar 29 '23 at 15:01
  • Yes I understand how `std::shared_from_this` works. I also understand that `std::shared_from_this` is heavily used in the `boost::asio` framework. I just don't think it is relevant to the question, that's why I uncommented it in the minimal example. In fact, my code is very close to the "best" example from https://en.cppreference.com/w/cpp/memory/enable_shared_from_this, with the exception that the factory function is a free function to simplify the use at the call site - which I thought was at the core of the question. – joergbrech Mar 29 '23 at 15:10
  • Commenting it merely ensure that the one thing it is essential for in the shared "session" pattern doesn't work. Also, you evilly dropped the `public` keyword :/ This could send many a programmer into head-scratchers with unexplainable "bad weak_ptr" exceptions... – sehe Mar 29 '23 at 15:13
  • 1
    Ah good catch! Also, the template parameters are missing. I will fix this. I will also uncomment as it doesn't hurt. – joergbrech Mar 29 '23 at 15:36