cpp_weekly icon indicating copy to clipboard operation
cpp_weekly copied to clipboard

Extension on Ep 421 on having RVO when returning std::optional.

Open lcarlier opened this issue 11 months ago • 3 comments

Channel

C++ weekly

Topics

You nicely explain how to get RVO when using std::optional.

There is actually one extra option which is to use std::make_optional. This will still make RVO works.

However, when using static class factory function the constructor must be public else RVO doesn't work. I'm wondering why.

See: https://godbolt.org/z/zooaofeE6

I think it is interesting to explain why RVO will not work when the constructor is private (which is very unfortunate...)

Having a private constructor make sense because of the following example

 // We need the factory function to create our TCP connection.
 // We want to avoid exception because of embedded systems requirements
 class TcpConnection {
private:
    TcpConnection(int fd): m_client_socket{fd} {
    }
public:

    TcpConnection(const TcpConnection&) = delete;
    TcpConnection(TcpConnection&&) = default;
    TcpConnection& operator=(const TcpConnection&) = delete;
    TcpConnection& operator=(TcpConnection&&) = default;

    static std::optional<TcpConnection> accept(uint32_t port = 8080) {
        // Step 1: Create a socket
        int server_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (server_fd == -1) {
            std::cerr << "Failed to create socket: " << std::strerror(errno) << "\n";
            return std::nullopt;
        }
        // All boiler plate to accept a connection
        return std::make_optional<TcpConnection>(client_socket);
        
    }

    inline void sendResponse(const SpanType data)
    {
        // use m_client_socket to send a response
    }

    SpanType receiveCommand()
    {
        // use m_client_socket to receive a message
    }

    ~TcpConnection()
    {
        if (m_client_socket != -1) ::close(m_client_socket);
    }
private:
    int m_client_socket;
};

Length

You choose.

lcarlier avatar Jan 29 '25 15:01 lcarlier

However, when using static class factory function the constructor must be public else RVO doesn't work. I'm wondering why.

It's because your code isn't the one calling the constructor anymore, instead it's being called by a standard library function that you aren't allowed or able to friend. Instead, you can have a public constructor which takes a tag type that can only be created by yourself and friends:

template<typename T>
class Key final
{
    friend T;
    constexpr Key(int) noexcept {}
};

By taking a Key<TcpConnection> parameter, you can have any constructor or member function be public but only able to be called by TcpConnection or one of its friends, even if it has to be called indirectly like by a standard library function. You construct the parameter as Key<TcpConnection>(0) and the library function just forwards the value that you provide.

LB-- avatar Feb 02 '25 23:02 LB--

Hello @LB--

It's because your code isn't the one calling the constructor anymore, instead it's being called by a standard library function that you aren't allowed or able to friend

I'm wondering, when my constructor is private, how can the standard library function call it? Shouldn't that be a compilation error? On that regards, I noticed that GCC 10.2 fails to compile my "CASE3" example while GCC 11.2 does compile it. Probably some kind of bug here.

Regarding your solution, I was trying to implement it but I'm not sure I got it right. I have something working but still the RVO isn't working (see CASE4): https://godbolt.org/z/cTxo51nfx

Is this what you meant?

lcarlier avatar Feb 03 '25 08:02 lcarlier

I'm wondering, when my constructor is private, how can the standard library function call it? Shouldn't that be a compilation error?

Maybe I misunderstood you, as indeed private and protected constructors cannot be utilized by anything in the standard library. If the code is compiling for you then perhaps something else unexpected is going on.

LB-- avatar Feb 04 '25 01:02 LB--