Szukaj…


Wprowadzenie

Usuwanie typu to zestaw technik służących do tworzenia typu, który może zapewnić jednolity interfejs dla różnych typów bazowych, jednocześnie ukrywając informacje o typie bazowym przed klientem. std::function<R(A...)> , która ma zdolność do przechowywania wywoływalnych obiektów różnych typów, jest prawdopodobnie najlepiej znanym przykładem kasowania typów w C ++.

Podstawowy mechanizm

Usuwanie typu to sposób na ukrycie typu obiektu przed kodem, który go używa, nawet jeśli nie pochodzi on ze wspólnej klasy bazowej. W ten sposób zapewnia pomost między światami statycznego polimorfizmu (szablony; w miejscu użytkowania dokładny typ musi być znany w czasie kompilacji, ale nie musi być deklarowany jako zgodny z interfejsem w definicji) i dynamicznym polimorfizmem (funkcje dziedziczenia i funkcje wirtualne; w miejscu użytkowania dokładny typ nie musi być znany w czasie kompilacji, ale musi zostać zadeklarowany jako zgodny z interfejsem w definicji).

Poniższy kod pokazuje podstawowy mechanizm usuwania typu.

#include <ostream>

class Printable
{
public:
  template <typename T>
  Printable(T value) : pValue(new Value<T>(value)) {}
  ~Printable() { delete pValue; }
  void print(std::ostream &os) const { pValue->print(os); }

private:
  Printable(Printable const &)        /* in C++1x: =delete */; // not implemented
  void operator = (Printable const &) /* in C++1x: =delete */; // not implemented
  struct ValueBase
  {
      virtual ~ValueBase() = default;
      virtual void print(std::ostream &) const = 0;
  };
  template <typename T>
  struct Value : ValueBase
  {
      Value(T const &t) : v(t) {}
      virtual void print(std::ostream &os) const { os << v; }
      T v;
  };
  ValueBase *pValue;
};

W witrynie użytkowania musi być widoczna tylko powyższa definicja, podobnie jak w przypadku klas podstawowych z funkcjami wirtualnymi. Na przykład:

#include <iostream>

void print_value(Printable const &p)
{
    p.print(std::cout);
}

Zauważ, że nie jest to szablon, ale normalna funkcja, która musi być zadeklarowana tylko w pliku nagłówkowym i może być zdefiniowana w pliku implementacji (w przeciwieństwie do szablonów, których definicja musi być widoczna w miejscu użycia).

Przy definicji konkretnego typu nie trzeba nic wiedzieć o Printable , wystarczy, że będzie on zgodny z interfejsem, podobnie jak w przypadku szablonów:

struct MyType { int i; };
ostream& operator << (ostream &os, MyType const &mc)
{
  return os << "MyType {" << mc.i << "}";
}

Możemy teraz przekazać obiekt tej klasy do funkcji zdefiniowanej powyżej:

MyType foo = { 42 };
print_value(foo);

Kasowanie do zwykłego typu z ręcznym vtable

C ++ rozwija się na tak zwanym typie regularnym (lub przynajmniej pseudo-regularnym).

Typ zwykły to typ, który można konstruować, przypisywać i przypisywać z niego poprzez kopiowanie lub przenoszenie, można go zniszczyć i porównywać na równi. Można go również zbudować bez argumentów. Wreszcie, obsługuje również kilka innych operacji, które są bardzo przydatne w różnych algorytmach std i kontenerach.

To jest główny plik , ale w C ++ 11 chciałbym dodać obsługę std::hash .

Będę używał ręcznego podejścia vtable, aby wpisać kasowanie tutaj.

using dtor_unique_ptr = std::unique_ptr<void, void(*)(void*)>;
template<class T, class...Args>
dtor_unique_ptr make_dtor_unique_ptr( Args&&... args ) {
  return {new T(std::forward<Args>(args)...), [](void* self){ delete static_cast<T*>(self); }};
}
struct regular_vtable {
  void(*copy_assign)(void* dest, void const* src); // T&=(T const&)
  void(*move_assign)(void* dest, void* src); // T&=(T&&)
  bool(*equals)(void const* lhs, void const* rhs); // T const&==T const&
  bool(*order)(void const* lhs, void const* rhs); // std::less<T>{}(T const&, T const&)
  std::size_t(*hash)(void const* self); // std::hash<T>{}(T const&)
  std::type_info const&(*type)(); // typeid(T)
  dtor_unique_ptr(*clone)(void const* self); // T(T const&)
};

template<class T>
regular_vtable make_regular_vtable() noexcept {
  return {
    [](void* dest, void const* src){ *static_cast<T*>(dest) = *static_cast<T const*>(src); },
    [](void* dest, void* src){ *static_cast<T*>(dest) = std::move(*static_cast<T*>(src)); },
    [](void const* lhs, void const* rhs){ return *static_cast<T const*>(lhs) == *static_cast<T const*>(rhs); },
    [](void const* lhs, void const* rhs) { return std::less<T>{}(*static_cast<T const*>(lhs),*static_cast<T const*>(rhs)); },
    [](void const* self){ return std::hash<T>{}(*static_cast<T const*>(self)); },
    []()->decltype(auto){ return typeid(T); },
    [](void const* self){ return make_dtor_unique_ptr<T>(*static_cast<T const*>(self)); }
  };
}
template<class T>
regular_vtable const* get_regular_vtable() noexcept {
  static const regular_vtable vtable=make_regular_vtable<T>();
  return &vtable;
}

struct regular_type {
  using self=regular_type;
  regular_vtable const* vtable = 0;
  dtor_unique_ptr ptr{nullptr, [](void*){}};
  
  bool empty() const { return !vtable; }

  template<class T, class...Args>
  void emplace( Args&&... args ) {
    ptr = make_dtor_unique_ptr<T>(std::forward<Args>(args)...);
    if (ptr)
      vtable = get_regular_vtable<T>();
    else
      vtable = nullptr;
  }
  friend bool operator==(regular_type const& lhs, regular_type const& rhs) {
    if (lhs.vtable != rhs.vtable) return false;
    return lhs.vtable->equals( lhs.ptr.get(), rhs.ptr.get() );
  }
  bool before(regular_type const& rhs) const {
    auto const& lhs = *this;
    if (!lhs.vtable || !rhs.vtable)
      return std::less<regular_vtable const*>{}(lhs.vtable,rhs.vtable);
    if (lhs.vtable != rhs.vtable)
      return lhs.vtable->type().before(rhs.vtable->type());
    return lhs.vtable->order( lhs.ptr.get(), rhs.ptr.get() );
  }
  // technically friend bool operator< that calls before is also required

  std::type_info const* type() const {
    if (!vtable) return nullptr;
    return &vtable->type();
  }
  regular_type(regular_type&& o):
    vtable(o.vtable),
    ptr(std::move(o.ptr))
  {
    o.vtable = nullptr;
  }
  friend void swap(regular_type& lhs, regular_type& rhs){
    std::swap(lhs.ptr, rhs.ptr);
    std::swap(lhs.vtable, rhs.vtable);
  }
  regular_type& operator=(regular_type&& o) {
    if (o.vtable == vtable) {
      vtable->move_assign(ptr.get(), o.ptr.get());
      return *this;
    }
    auto tmp = std::move(o);
    swap(*this, tmp);
    return *this;
  }
  regular_type(regular_type const& o):
    vtable(o.vtable),
    ptr(o.vtable?o.vtable->clone(o.ptr.get()):dtor_unique_ptr{nullptr, [](void*){}})
  {
    if (!ptr && vtable) vtable = nullptr;
  }
  regular_type& operator=(regular_type const& o) {
    if (o.vtable == vtable) {
      vtable->copy_assign(ptr.get(), o.ptr.get());
      return *this;
    }
    auto tmp = o;
    swap(*this, tmp);
    return *this;
  }
  std::size_t hash() const {
    if (!vtable) return 0;
    return vtable->hash(ptr.get());
  }
  template<class T,
    std::enable_if_t< !std::is_same<std::decay_t<T>, regular_type>{}, int>* =nullptr
  >
  regular_type(T&& t) {
    emplace<std::decay_t<T>>(std::forward<T>(t));
  }
};
namespace std {
  template<>
  struct hash<regular_type> {
    std::size_t operator()( regular_type const& r )const {
      return r.hash();
    }
  };
  template<>
  struct less<regular_type> {
    bool operator()( regular_type const& lhs, regular_type const& rhs ) const {
      return lhs.before(rhs);
    }
  };
}    

przykład na żywo .

Taki regularny typ może być użyty jako klucz dla std::map lub std::unordered_map który akceptuje cokolwiek regularnego dla klucza, na przykład:

std::map<regular_type, std::any>

byłaby w zasadzie mapą od zwykłej mapy do wszystkiego, co można skopiować.

W przeciwieństwie do any , mój regular_type nie optymalizuje małych obiektów ani nie wspiera regular_type oryginalnych danych. Odzyskanie oryginalnego typu nie jest trudne.

Optymalizacja małych obiektów wymaga przechowywania wyrównanego bufora pamięci w regular_type i starannego dostosowania separatora ptr aby tylko zniszczyć obiekt, a nie go usunąć.

Chciałbym zacząć od make_dtor_unique_ptr i nauczyć go, jak czasami przechowywać dane w buforze, a następnie na stercie, jeśli nie ma miejsca w buforze. To może wystarczyć.

Opcja „std :: function” tylko do ruchu

typ std::function usuwa do kilku operacji. Jedną z rzeczy, których wymaga, jest możliwość zapisania przechowywanej wartości.

Powoduje to problemy w kilku kontekstach, takich jak lambdas przechowujący unikalne ptrs. Jeśli używasz funkcji std::function w kontekście, w którym kopiowanie nie ma znaczenia, na przykład w puli wątków, w której zadania są wysyłane do wątków, wymaganie to może narzucić dodatkowe koszty.

W szczególności std::packaged_task<Sig> jest obiektem, który można wywoływać tylko w ruchu. Możesz przechowywać std::packaged_task<R(Args...)> w std::packaged_task<void(Args...)> , ale jest to dość ciężki i niejasny sposób na utworzenie tylko ruchu wywoływana klasa kasowania typu.

Tak więc task . To pokazuje, jak można napisać prosty typ std::function . details::task_pimpl<...> konstruktor kopiowania (co wymagałoby także dodania metody clone do details::task_pimpl<...> ).

template<class Sig>
struct task;

// putting it in a namespace allows us to specialize it nicely for void return value:
namespace details {
  template<class R, class...Args>
  struct task_pimpl {
    virtual R invoke(Args&&...args) const = 0;
    virtual ~task_pimpl() {};
    virtual const std::type_info& target_type() const = 0;
  };

  // store an F.  invoke(Args&&...) calls the f
  template<class F, class R, class...Args>
  struct task_pimpl_impl:task_pimpl<R,Args...> {
    F f;
    template<class Fin>
    task_pimpl_impl( Fin&& fin ):f(std::forward<Fin>(fin)) {}
    virtual R invoke(Args&&...args) const final override {
      return f(std::forward<Args>(args)...);
    }
    virtual const std::type_info& target_type() const final override {
      return typeid(F);
    }
  };

  // the void version discards the return value of f:
  template<class F, class...Args>
  struct task_pimpl_impl<F,void,Args...>:task_pimpl<void,Args...> {
    F f;
    template<class Fin>
    task_pimpl_impl( Fin&& fin ):f(std::forward<Fin>(fin)) {}
    virtual void invoke(Args&&...args) const final override {
      f(std::forward<Args>(args)...);
    }
    virtual const std::type_info& target_type() const final override {
      return typeid(F);
    }
  };
};

template<class R, class...Args>
struct task<R(Args...)> {
  // semi-regular:
  task()=default;
  task(task&&)=default;
  // no copy

private:
  // aliases to make some SFINAE code below less ugly:
  template<class F>
  using call_r = std::result_of_t<F const&(Args...)>;
  template<class F>
  using is_task = std::is_same<std::decay_t<F>, task>;
public:
  // can be constructed from a callable F
  template<class F,
    // that can be invoked with Args... and converted-to-R:
    class= decltype( (R)(std::declval<call_r<F>>()) ),
    // and is not this same type:
    std::enable_if_t<!is_task<F>{}, int>* = nullptr
  >
  task(F&& f):
    m_pImpl( make_pimpl(std::forward<F>(f)) )
  {}

  // the meat: the call operator        
  R operator()(Args... args)const {
        return m_pImpl->invoke( std::forward<Args>(args)... );
  }
  explicit operator bool() const {
    return (bool)m_pImpl;
  }
  void swap( task& o ) {
    std::swap( m_pImpl, o.m_pImpl );
  }
  template<class F>
  void assign( F&& f ) {
    m_pImpl = make_pimpl(std::forward<F>(f));    
  }
  // Part of the std::function interface:
  const std::type_info& target_type() const {
    if (!*this) return typeid(void);
    return m_pImpl->target_type();
  }
  template< class T >
  T* target() {
    return target_impl<T>();
  }
  template< class T >
  const T* target() const {
    return target_impl<T>();
  }
  // compare with nullptr    :    
  friend bool operator==( std::nullptr_t, task const& self ) { return !self; }
  friend bool operator==( task const& self, std::nullptr_t ) { return !self; }
  friend bool operator!=( std::nullptr_t, task const& self ) { return !!self; }
  friend bool operator!=( task const& self, std::nullptr_t ) { return !!self; }
private:
  template<class T>
  using pimpl_t = details::task_pimpl_impl<T, R, Args...>;

  template<class F>
  static auto make_pimpl( F&& f ) {
    using dF=std::decay_t<F>;
    using pImpl_t = pimpl_t<dF>;
    return std::make_unique<pImpl_t>(std::forward<F>(f));
  }
  std::unique_ptr<details::task_pimpl<R,Args...>> m_pImpl;

  template< class T >
  T* target_impl() const {
    return dynamic_cast<pimpl_t<T>*>(m_pImpl.get());
  }
};

Aby ta biblioteka była godna, warto dodać niewielką optymalizację bufora, aby nie zapisywała wszystkich wywołań na stercie.

Dodanie SBO wymagałoby task(task&&) innego niż domyślne task(task&&) , niektórych std::aligned_storage_t w klasie, m_pImpl unique_ptr z deleterem, który można ustawić na opcję tylko zniszczyć (i nie przywracać pamięci na stos), oraz emplace_move_to( void* ) = 0 w task_pimpl .

przykład na żywo powyższego kodu (bez SBO).

Kasowanie do ciągłego bufora T

Nie wszystkie operacje usuwania typu obejmują dziedziczenie wirtualne, przydziały, umieszczanie nowych, a nawet wskaźniki funkcji.

To, co powoduje, że typ jest usuwany, to to, że opisuje (zestaw) zachowanie (zachowania) i bierze dowolny typ, który obsługuje to zachowanie, i je zawija. Wszystkie informacje, których nie ma w tym zestawie zachowań, są „zapomniane” lub „usuwane”.

array_view pobiera przychodzący zakres lub typ kontenera i usuwa wszystko oprócz tego, że jest ciągłym buforem T

// helper traits for SFINAE:
template<class T>
using data_t = decltype( std::declval<T>().data() );

template<class Src, class T>
using compatible_data = std::integral_constant<bool, std::is_same< data_t<Src>, T* >{} || std::is_same< data_t<Src>, std::remove_const_t<T>* >{}>;

template<class T>
struct array_view {
  // the core of the class:
  T* b=nullptr;
  T* e=nullptr;
  T* begin() const { return b; }
  T* end() const { return e; }

  // provide the expected methods of a good contiguous range:
  T* data() const { return begin(); }
  bool empty() const { return begin()==end(); }
  std::size_t size() const { return end()-begin(); }

  T& operator[](std::size_t i)const{ return begin()[i]; }
  T& front()const{ return *begin(); }
  T& back()const{ return *(end()-1); }

  // useful helpers that let you generate other ranges from this one
  // quickly and safely:
  array_view without_front( std::size_t i=1 ) const {
    i = (std::min)(i, size());
    return {begin()+i, end()};
  }
  array_view without_back( std::size_t i=1 ) const {
    i = (std::min)(i, size());
    return {begin(), end()-i};
  }

  // array_view is plain old data, so default copy:
  array_view(array_view const&)=default;
  // generates a null, empty range:
  array_view()=default;

  // final constructor:
  array_view(T* s, T* f):b(s),e(f) {}
  // start and length is useful in my experience:
  array_view(T* s, std::size_t length):array_view(s, s+length) {}

  // SFINAE constructor that takes any .data() supporting container
  // or other range in one fell swoop:
  template<class Src,
    std::enable_if_t< compatible_data<std::remove_reference_t<Src>&, T >{}, int>* =nullptr,
    std::enable_if_t< !std::is_same<std::decay_t<Src>, array_view >{}, int>* =nullptr
  >
  array_view( Src&& src ):
    array_view( src.data(), src.size() )
  {}

  // array constructor:
  template<std::size_t N>
  array_view( T(&arr)[N] ):array_view(arr, N) {}

  // initializer list, allowing {} based:
  template<class U,
    std::enable_if_t< std::is_same<const U, T>{}, int>* =nullptr
  >
  array_view( std::initializer_list<U> il ):array_view(il.begin(), il.end()) {}
};

array_view wykonuje żadnego pojemnika, który obsługuje .data() przekazujących wskaźnik do T i .size() sposobu, lub tablicę i usuwa go do bycia losowym dostępie ciągłych T S.

Może to wymagać std::vector<T> , std::string<T> a std::array<T, N> a T[37] , listy inicjalizacyjnej (w tym opartych na {} ) lub czegoś innego makijaż, który go obsługuje (przez T* x.data() i size_t x.size() ).

W takim przypadku dane, które możemy wyodrębnić z tego, co usuwamy, wraz z naszym stanem „nie posiadania” widoku, oznacza, że nie musimy przydzielać pamięci ani pisać niestandardowych funkcji zależnych od typu.

Przykład na żywo .

Ulepszeniem byłoby użycie data nie będących elementami członkowskimi i size nie będących elementami członkowskimi w kontekście włączonym ADL.

Kasowanie typów Kasowanie typów za pomocą std :: any

W tym przykładzie użyto C ++ 14 i boost::any . W C ++ 17 możesz zamiast tego zamieniać w std::any .

Składnia, którą otrzymujemy to:

const auto print =
  make_any_method<void(std::ostream&)>([](auto&& p, std::ostream& t){ t << p << "\n"; });

super_any<decltype(print)> a = 7;

(a->*print)(std::cout);

co jest prawie optymalne.

Ten przykład jest oparty na pracy @dyp i @cpplearner, a także mojej własnej.


Najpierw używamy tagu do przekazywania typów:

template<class T>struct tag_t{constexpr tag_t(){};};
template<class T>constexpr tag_t<T> tag{};

Ta klasa cech przechowuje sygnaturę z any_method :

To tworzy typ wskaźnika funkcji i fabrykę dla wspomnianych wskaźników funkcji, biorąc pod uwagę any_method :

template<class any_method>
using any_sig_from_method = typename any_method::signature;

template<class any_method, class Sig=any_sig_from_method<any_method>>
struct any_method_function;

template<class any_method, class R, class...Args>
struct any_method_function<any_method, R(Args...)>
{
  template<class T>
  using decorate = std::conditional_t< any_method::is_const, T const, T >;
  
  using any = decorate<boost::any>;
  
  using type = R(*)(any&, any_method const*, Args&&...);
  template<class T>
  type operator()( tag_t<T> )const{
    return +[](any& self, any_method const* method, Args&&...args) {
      return (*method)( boost::any_cast<decorate<T>&>(self), decltype(args)(args)... );
    };
  }
};

any_method_function::type to typ wskaźnika funkcji, który będziemy przechowywać obok instancji. any_method_function::operator() pobiera tag_t<T> i zapisuje niestandardową instancję typu any_method_function::type która zakłada, że any& będzie T

Chcemy mieć możliwość wymazywania więcej niż jednej metody na raz. Więc łączymy je w krotkę i piszemy opakowanie pomocnicze, aby wbijać krotkę do magazynu statycznego dla poszczególnych rodzajów i utrzymywać wskaźnik do nich.

template<class...any_methods>
using any_method_tuple = std::tuple< typename any_method_function<any_methods>::type... >;

template<class...any_methods, class T>
any_method_tuple<any_methods...> make_vtable( tag_t<T> ) {
  return std::make_tuple(
    any_method_function<any_methods>{}(tag<T>)...
  );
}

template<class...methods>
struct any_methods {
private:
  any_method_tuple<methods...> const* vtable = 0;
  template<class T>
  static any_method_tuple<methods...> const* get_vtable( tag_t<T> ) {
    static const auto table = make_vtable<methods...>(tag<T>);
    return &table;
  }
public:
  any_methods() = default;
  template<class T>
  any_methods( tag_t<T> ): vtable(get_vtable(tag<T>)) {}
  any_methods& operator=(any_methods const&)=default;
  template<class T>
  void change_type( tag_t<T> ={} ) { vtable = get_vtable(tag<T>); }
    
  template<class any_method>
  auto get_invoker( tag_t<any_method> ={} ) const {
    return std::get<typename any_method_function<any_method>::type>( *vtable );
  }
};

Możemy to specjalizować w przypadkach, w których vtable jest mały (na przykład 1 element) i w tych przypadkach użyć bezpośrednich wskaźników przechowywanych w klasie.

Teraz zaczynamy super_any . Używam super_any_t aby nieco ułatwić deklarację super_any .

template<class...methods>
struct super_any_t;

Przeszukuje metody, które super obsługuje SFINAE i lepsze komunikaty o błędach:

template<class super_any, class method>
struct super_method_applies_helper : std::false_type {};

template<class M0, class...Methods, class method>
struct super_method_applies_helper<super_any_t<M0, Methods...>, method> :
    std::integral_constant<bool, std::is_same<M0, method>{}  || super_method_applies_helper<super_any_t<Methods...>, method>{}>
{};

template<class...methods, class method>
auto super_method_test( super_any_t<methods...> const&, tag_t<method> )
{
  return std::integral_constant<bool, super_method_applies_helper< super_any_t<methods...>, method >{} && method::is_const >{};
}
template<class...methods, class method>
auto super_method_test( super_any_t<methods...>&, tag_t<method> )
{
  return std::integral_constant<bool, super_method_applies_helper< super_any_t<methods...>, method >{} >{};
}

template<class super_any, class method>
struct super_method_applies:
    decltype( super_method_test( std::declval<super_any>(), tag<method> ) )
{};

Następnie tworzymy typ any_method . any_method to pseudo-metoda-wskaźnik. Tworzymy go globalnie i const przy użyciu składni, takiej jak:

const auto print=make_any_method( [](auto&&self, auto&&os){ os << self; } );

lub w C ++ 17:

const any_method print=[](auto&&self, auto&&os){ os << self; };

Zauważ, że użycie innej niż lambda może sprawić, że wszystko będzie owłosione, ponieważ używamy tego typu do wyszukiwania. Można to naprawić, ale sprawi, że ten przykład będzie dłuższy niż już jest. Dlatego zawsze inicjuj dowolną metodę z lambda lub z typu sparametryzowanego na lambda.

template<class Sig, bool const_method, class F>
struct any_method {
  using signature=Sig;
  enum{is_const=const_method};
private:
  F f;
public:

  template<class Any,
    // SFINAE testing that one of the Anys's matches this type:
    std::enable_if_t< super_method_applies< Any&&, any_method >{}, int>* =nullptr
  >
  friend auto operator->*( Any&& self, any_method const& m ) {
    // we don't use the value of the any_method, because each any_method has
    // a unique type (!) and we check that one of the auto*'s in the super_any
    // already has a pointer to us.  We then dispatch to the corresponding
    // any_method_data...

    return [&self, invoke = self.get_invoker(tag<any_method>), m](auto&&...args)->decltype(auto)
    {
      return invoke( decltype(self)(self), &m, decltype(args)(args)... );
    };
  }
  any_method( F fin ):f(std::move(fin)) {}
  
  template<class...Args>
  decltype(auto) operator()(Args&&...args)const {
    return f(std::forward<Args>(args)...);
  }
};

Metoda fabryczna, niepotrzebna w C ++ 17 Wierzę:

template<class Sig, bool is_const=false, class F>
any_method<Sig, is_const, std::decay_t<F>>
make_any_method( F&& f ) {
  return {std::forward<F>(f)};
}

Jest to any powiększone. Jest to zarówno any , a to niesie około wiązkę typu Erasure wskaźników funkcji, które zmieniają się, gdy zawarte any robi:

template<class... methods>
struct super_any_t:boost::any, any_methods<methods...> {
  using vtable=any_methods<methods...>;
public:
  template<class T,
    std::enable_if_t< !std::is_base_of<super_any_t, std::decay_t<T>>{}, int> =0
  >
  super_any_t( T&& t ):
    boost::any( std::forward<T>(t) )
  {
    using dT=std::decay_t<T>;
    this->change_type( tag<dT> );
  }
  
  boost::any& as_any()&{return *this;}
  boost::any&& as_any()&&{return std::move(*this);}
  boost::any const& as_any()const&{return *this;}
  super_any_t()=default;
  super_any_t(super_any_t&& o):
    boost::any( std::move( o.as_any() ) ),
    vtable(o)
  {}
  super_any_t(super_any_t const& o):
    boost::any( o.as_any() ),
    vtable(o)
  {}
  template<class S,
    std::enable_if_t< std::is_same<std::decay_t<S>, super_any_t>{}, int> =0
  >
  super_any_t( S&& o ):
    boost::any( std::forward<S>(o).as_any() ),
    vtable(o)
  {}
  super_any_t& operator=(super_any_t&&)=default;
  super_any_t& operator=(super_any_t const&)=default;
  
  template<class T,
    std::enable_if_t< !std::is_same<std::decay_t<T>, super_any_t>{}, int>* =nullptr
  >
  super_any_t& operator=( T&& t ) {
    ((boost::any&)*this) = std::forward<T>(t);
    using dT=std::decay_t<T>;
    this->change_type( tag<dT> );
    return *this;
  }  
};

Ponieważ przechowujemy any_method s jako obiekty const , to sprawia, że super_any jest trochę łatwiejsze:

template<class...Ts>
using super_any = super_any_t< std::remove_cv_t<Ts>... >;

Kod testowy:

const auto print = make_any_method<void(std::ostream&)>([](auto&& p, std::ostream& t){ t << p << "\n"; });
const auto wprint = make_any_method<void(std::wostream&)>([](auto&& p, std::wostream& os ){ os << p << L"\n"; });

int main()
{
  super_any<decltype(print), decltype(wprint)> a = 7;
  super_any<decltype(print), decltype(wprint)> a2 = 7;

  (a->*print)(std::cout);
  (a->*wprint)(std::wcout);
}

przykład na żywo .

Oryginalnie zamieszczony tutaj w SO własnym pytaniu i odpowiedzi (a osoby wymienione powyżej pomogły w realizacji).



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow