Why can't I have std::optional<T> where T is abstract?

This does not work:

struct Type {
    virtual bool func(const std::string& val) const noexcept = 0;
}

// in main
optional<Type> = some_function_returning_optional_type();

and fails with a error message:

error: cannot declare field 'std::experimental::fundamentals_v1::_Optional_base<Type, false>::<anonymous union>::_M_payload' to be of abstract type 'Type'

Changing the Type to have a non-pure function works, but is not appropriate in this case, because there cannot be an instance of Type in my code, only classes which inherit from it should be able to exist.

2 answers

  • answered 2017-10-11 10:05 Vittorio Romeo

    std::optional<T> stores its value in-place - it therefore needs to know the size of T to work correctly, and T must be a concrete type that can be instantiated. You can think of std::optional<T> as:

    template <typename T>
    struct optional
    {
        std::aligned_storage_t<sizeof(T), alignof(T)> _data;
        bool _set;
    };
    

    An abstract type represents an interface - polymorphism and some sort of indirection are required to work with abstract types. std::optional doesn't have any indirection by design.

  • answered 2017-10-11 10:05 Richard Hodges

    Your proposal of optional will of course work but it would offend me to have to write

    x.value()->do_something();
    

    and I'd be concerned that users might do something daft:

    x.value().reset();  // now what?
    

    We can achieve polymorphism with a non-polymorphic interface by using a wrapper.

    Here's one way:

    #include <optional>
    #include <iostream>
    
    // the Foo interface/base class
    struct Foo
    {
        virtual ~Foo() = default;
        virtual Foo* clone() const { return new Foo(*this); }
    
        virtual void do_something() {
            std::cout << "something Fooey\n";
        }
    };
    
    // a service for managing Foo and classes derived from Foo
    struct FooService
    {
        template<class Arg>
        Foo* clone(Arg&& arg)
        {
            using d_type = std::decay_t<Arg>; 
            return new d_type(std::forward<Arg>(arg));
        }
    
        template<class Arg>
        Foo* clone(Foo* arg)
        {
            return arg->clone();
        }
    
        Foo* release(Foo*& other) noexcept
        {
            auto tmp = other;
            other = nullptr;
            return tmp;
        }
    };
    
    // implement the Foo interface in terms of a pimpl
    template<class Holder>
    struct BasicFoo
    {
    
        decltype(auto) do_something() {
            return get().do_something();
        }
    
    
    
    private:
        Foo& get() noexcept { return static_cast<Holder*>(this)->get_impl(); }
        Foo const& get() const noexcept { return static_cast<Holder const*>(this)->get_impl(); }
    };
    
    
    // a type for holding anything derived from a Foo
    // can be initialised by anything Foo-like and handles copy/move correctly
    struct FooHolder : BasicFoo<FooHolder>
    {
        template
        <
            class Arg,
            std::enable_if_t
            <
                std::is_base_of_v<Foo, std::decay_t<Arg>>
            >* = nullptr
        >
        FooHolder(Arg&& arg)
        : service_()
        , ptr_(service_.clone(std::forward<Arg>(arg)))
        {}
    
        FooHolder(FooHolder const& other)
        : service_()
        , ptr_(other.ptr_->clone())
        {
        }
    
        FooHolder(FooHolder && other) noexcept
        : service_()
        , ptr_(service_.release(other.ptr_))
        {
        }
    
        FooHolder& operator=(FooHolder const& other)
        {
            auto tmp = other;
            std::swap(ptr_, tmp.ptr_);
            return *this;
        }
    
        FooHolder& operator=(FooHolder && other) noexcept
        {
            auto tmp = std::move(other);
            std::swap(ptr_, tmp.ptr_);
            return *this;
        }
    
        ~FooHolder()
        {
            delete ptr_;
        }
    
        Foo& get_impl() noexcept { return *ptr_; }
        Foo const& get_impl() const noexcept { return *ptr_; }
    
        FooService service_;
        Foo* ptr_;
    };
    
    // now we can supply as many overrides of Foo as we like    
    struct Bar : Foo
    {
        virtual Foo* clone() const { return FooService().clone(*this); }
    
        virtual void do_something() {
            std::cout << "something Barey\n";
        }
    
    };
    
    int main()
    {
        std::optional<FooHolder> opt;
    
        // note that we're initialising cleanly    
        opt = Bar {};
    
        // and we don't expose the pointer so the user can't
        // destroy the pimpl accidentally
        opt.value().do_something();
    }