c++constructorprivatepublic

how to return a std::expect<T, E> from a function where T's move/copy constructor are deleted and has a private constructor


Currently I have this code, which works fine:

#pragma once

#include <expected>
#include <wayland-server.h>

class Display
{
private:
    struct wl_display *ptr;

public:
    enum class Error
    {
        CannotCreateDisplay,
    };

    Display() = delete;
    Display(struct wl_display *ptr)
        : ptr{ptr}
    {
    }
    Display(const Display &) = delete;
    Display(Display &&) = delete;
    Display &operator=(const Display &) = delete;
    Display &operator=(Display &&) = delete;
    ~Display()
    {
        wl_display_destroy_clients(this->ptr);
        wl_display_destroy(this->ptr);
    }

    static std::expected<Display, Error> create()
    {
        struct wl_display *ptr{wl_display_create()};
        if (ptr == nullptr) {
            return std::unexpected{Error::CannotCreateDisplay};
        }
        return {ptr};
    }
};

I would like to make the Display(struct wl_display *) constructor private but I can't because otherwise the create would complains about not finding any viable constructor to call for Display.

One solution I found, is to make a move constructor for Display but is there any else solutions for this ?

class Display
{
private:
    struct wl_display *ptr;
    Display(struct wl_display *ptr)
        : ptr{ptr}
    {
    }

public:
    enum class Error
    {
        CannotCreateDisplay,
    };

    Display() = delete;
    Display(const Display &) = delete;
    Display(Display &&) = delete;
    Display &operator=(const Display &) = delete;
    Display &operator=(Display &&) = delete;
    ~Display()
    {
        wl_display_destroy_clients(this->ptr);
        wl_display_destroy(this->ptr);
    }

    static std::expected<Display, Error> create()
    {
        struct wl_display *ptr{wl_display_create()};
        if (ptr == nullptr) {
            return std::unexpected{Error::CannotCreateDisplay};
        }
        return {ptr}; // ERROR: can't find any viable constructor
    }
};

Solution

  • Surely you can, by making use of the monadic operations of the std::expected. The same method can be applied to std::optional.

    Instead of

    return {ptr};
    

    You can detour via another std::expected, and transform the value in-place (credit to @Artyer for making it more succinct):

    return std::expected<void, Error>{}.transform(
        [&] {
            return Display{ptr};
        }
    );
    

    Demo: https://godbolt.org/z/x4aoWsTeT


    Here is how it works. Without the detour, you have to choose between constructing Display directly inside std::expected, or constructing Display outside and move it.

    The former requires invoking the private constructor directly inside the constructor of std::expected. Since std::expected is not a friend, this is doomed to fail.

    The latter only works if Display is movable. This is exactly what OP wants to avoid.

    With the detour, we are providing a (sort of public) callback function which calls the private constructor in a context that it is allowed. This is similar to the passkey idiom mentioned in the comments: both provide a public construction function that has very limited access. In the passkey idiom, the access is protected by a struct that serves as an authorization token; In this method, the access is protected naturally by scope, as the lambda is a local variable.