2017-10-11 12 views
1

Cela ne fonctionne pas:Pourquoi je ne peux pas avoir std :: optionnel <T> où T est abstrait?

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

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

et échoue avec un message d'erreur:

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

Modification du Type d'avoir une marche fonction non-pure, mais ne convient pas dans ce cas, car il ne peut pas être une instance de Type dans mon code, seules les classes qui en héritent devraient pouvoir exister.

+1

Comment pouvez-vous même instancier le type abstrait à mettre en option? Si vous êtes en train d'instancier un type concret, mais que vous utilisez un pointeur vers le type abstrait, vous pouvez placer un pointeur (ou un pointeur intelligent) dans votre option. Notez cependant que vous avez seulement besoin d'un optionnel si 'nullptr' n'est pas suffisant et que vous devez gérer un cas spécial quand même un' nullptr' n'a pas pu être créé. – AMA

Répondre

3

std::optional<T> stocke sa valeur en place - il doit donc connaître la taille de T pour fonctionner correctement, et T doit être un type concret qui peut être instancié. Vous pouvez penser std::optional<T> comme:

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

Un type abstrait représente une interface - polymorphisme et une sorte de indirection sont nécessaires pour travailler avec des types abstraits. std::optional n'a pas d'indirection par conception.

+0

Donc, quand je comprends cela correctement, cacher le 'Type' derrière un pointeur (intelligent) ferait l'affaire? – musicmatze

+4

@musicmatze: vous pouvez avoir un 'std :: optionnel >'. Mais pourquoi ne pas utiliser 'std :: unique_ptr ' alors? –

+1

Le polymorphisme @musicmatze nécessite toujours une indirection. – user2079303

0

Votre proposition facultative volonté du travail bien sûr, mais il me offenser d'avoir à écrire

x.value()->do_something(); 

et je serais concerné que les utilisateurs pourraient faire quelque chose daft:

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

Nous pouvons réaliser un polymorphisme avec une interface non polymorphe en utilisant un wrapper.

est ici une façon:

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