c++c++17stdoptional

Can I have a std::optional<T> if T is neither constructible nor copyable nor movable?


The only requirement to type parameter of std::optional<T> class template mentioned in [optional.optional.general] p3 is that type T is Destructible.

Suppose I have a very restrictive class which meets that requirement:

struct R
{
    R(const R&) = delete;
    R& operator=(const R&) = delete;
    R(R&&) = delete;
    R& operator=(R&&) = delete;

    // This works thanks to guaranteed copy elision since C++17.
    static R create()
    {
        return R();
    }

  private:
    R() = default;
};

Yet that class is still perfectly usable:

void test_1()
{
    R obj = R::create();
    /* use obj */
}

The question is: can I really have a usable object of type std::optional<R>? By "usable" I mean an object that contains value. If yes, then how do I construct that value there?

The following code obviously fails because every constructor of R is inaccessible:

void test_2()
{
    std::optional<R> o(R::create());   // Error: no matching constructor
}

Godbolt example link.


Edit:

Wouldn't it be nice if std::optional had a constructor that accepted a builder?

template <typename Builder>
optional::optional(disabmiguating_tag, Builder f)
 : my_internal_union(f())
{
}

with apropriate constructor of my_internal_union of course...


Solution

  • You can use a a type with an operator R:

    struct Builder {
        operator R() const { return R::create(); }
    };
    
    int main() {
        std::optional<R> o(Builder{});
    }
    

    See live example at Compiler Explorer

    This works because the std::optional<R> o is constructed via the constructor #8 on cppreference, which means that the R that inhabits o is constructed as if by calling

    R(std::move(builder))
    

    ... where builder is a reference to the Builder{} we have passed in.

    operator R returns a prvalue, so the returned R::create() and the constructed R are the same object, by virtue of C++17 guaranteed copy elision.