为什么我不能使用std :: optional <t>其中T是抽象的?

时间:2017-10-11 10:05:40

标签: c++ templates virtual abstract optional

这不起作用:

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

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

并失败并显示错误消息:

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

更改Type以使非纯函数有效,但在这种情况下不合适,因为在我的代码中不能有Type的实例,只有从中继承的类才应该能够存在。

2 个答案:

答案 0 :(得分:4)

std::optional<T>将其值存储到位 - 因此需要知道T的大小才能正常工作,T必须是可以实例化的具体类型。您可以将std::optional<T>视为:

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

抽象类型表示接口 - 多态性和某种间接是处理抽象类型所必需的。 std::optional没有任何设计间接性。

答案 1 :(得分:0)

你的可选选项当然有效,但我不得不写

x.value()->do_something();

我担心用户可能会做些蠢事:

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

我们可以通过使用包装器来实现非多态接口的多态性。

这是一种方式:

#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();
}