Suche…


Einführung

Typlöschung ist ein Satz von Techniken zum Erstellen eines Typs, der eine einheitliche Schnittstelle zu verschiedenen zugrunde liegenden Typen bereitstellen kann, während die zugrunde liegenden Typinformationen vor dem Client ausgeblendet werden. std::function<R(A...)> , die aufrufbare Objekte verschiedener Typen enthalten kann, ist vielleicht das bekannteste Beispiel für das Löschen von Typen in C ++.

Grundmechanismus

Mit der Typenlöschung können Sie den Typ eines Objekts mithilfe von Code vor Code ausblenden, auch wenn es nicht von einer allgemeinen Basisklasse abgeleitet ist. Dadurch wird eine Brücke zwischen den Welten des statischen Polymorphismus (Templates; am Ort der Verwendung muss der genaue Typ zum Zeitpunkt des Kompilierens bekannt sein, muss aber nicht als konform mit einer Schnittstelle definiert werden) und dynamischem Polymorphismus (Vererbung und virtuelle Funktionen; am Ort der Verwendung muss der genaue Typ nicht zur Kompilierzeit bekannt sein, sondern muss bei der Definition als konform mit einer Schnittstelle deklariert werden).

Der folgende Code zeigt den grundlegenden Mechanismus zum Löschen von Typen.

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

Auf der Benutzungssite muss nur die obige Definition sichtbar sein, genau wie bei Basisklassen mit virtuellen Funktionen. Zum Beispiel:

#include <iostream>

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

Beachten Sie, dass dies keine Vorlage ist, sondern eine normale Funktion, die nur in einer Header-Datei deklariert werden muss und in einer Implementierungsdatei definiert werden kann (im Gegensatz zu Vorlagen, deren Definition am Verwendungsort sichtbar sein muss).

Bei der Definition des konkreten Typs muss über Printable nichts bekannt sein, es muss lediglich eine Schnittstelle wie bei Vorlagen verwendet werden:

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

Wir können nun ein Objekt dieser Klasse an die oben definierte Funktion übergeben:

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

Löschen auf einen normalen Typ mit manueller vtable

C ++ basiert auf einem so genannten Regular-Typ (oder zumindest Pseudo-Regular).

Ein regulärer Typ ist ein Typ, der durch Kopieren oder Verschieben konstruiert und zugewiesen und zugewiesen werden kann, der zerstört werden kann und mit dem verglichen werden kann. Es kann auch ohne Argumente konstruiert werden. Schließlich werden auch einige andere Operationen unterstützt, die in verschiedenen std und Containern sehr nützlich sind.

Dies ist das Stammpapier , aber in C ++ 11 möchte die Unterstützung für std::hash hinzugefügt werden.

Ich werde hier den manuellen vtable-Ansatz verwenden, um das Löschen von Texten vorzunehmen.

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

Live-Beispiel .

Ein solcher regulärer Typ kann als Schlüssel für eine std::map oder eine std::unordered_map , die reguläre std::unordered_map für einen Schlüssel akzeptiert, wie z.

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

Im Grunde wäre es eine Karte von einem anderen regulären zu allem, was kopierbar ist.

Anders als any führt mein regular_type keine Optimierung für kleine Objekte durch und unterstützt auch nicht die regular_type der Originaldaten. Den ursprünglichen Typ zurückzubekommen, ist nicht schwer.

Kleine Objekt Optimierung erfordert , dass wir speichern einen ausgerichteten Speicherpuffer innerhalb der regular_type und sorgfältig die deleter des zwicken ptr nur das Objekt zu zerstören und sie nicht löschen.

Ich würde bei make_dtor_unique_ptr und es lehren, wie man manchmal die Daten in einem Puffer speichert, und dann im Heap, wenn kein Speicherplatz im Puffer vorhanden ist. Das kann ausreichen.

Eine Nur-Bewegung-Funktion "std ::"

std::function löscht bis auf wenige Operationen. Voraussetzung ist, dass der gespeicherte Wert kopierbar ist.

Dies führt zu Problemen in einigen Kontexten, z. B. bei Lambdas, die eindeutige Ptrs speichern. Wenn Sie die std::function in einem Kontext verwenden, in dem das Kopieren keine Rolle spielt, z. B. in einem Thread-Pool, in dem Sie Aufgaben an Threads versenden, kann diese Anforderung Overhead verursachen.

Insbesondere ist std::packaged_task<Sig> ein aufrufbares Objekt, das nur zum Verschieben verwendet wird. Sie können eine std::packaged_task<R(Args...)> in einer std::packaged_task<void(Args...)> , aber dies ist eine ziemlich schwere und obskure Methode, um nur eine Bewegung zu erstellen aufrufbare Typlöschungsklasse.

Also die task . Dies zeigt, wie Sie einen einfachen std::function schreiben können. Ich weggelassen , um die Copy - Konstruktor (die einen bedeuten würde das Hinzufügen clone - Methode details::task_pimpl<...> als auch).

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

Um diese Bibliothek würdig zu machen, möchten Sie eine kleine Pufferoptimierung hinzufügen, sodass nicht alle aufrufbaren Objekte auf dem Heap gespeichert werden.

Für das Hinzufügen von SBO sind eine nicht standardmäßige task(task&&) , ein Teil der Klasse std::aligned_storage_t , ein m_pImpl unique_ptr mit einem Deleter m_pImpl , der auf die Nur-Zerstörungsfunktion gesetzt werden kann (und nicht den Speicher an den Heap emplace_move_to( void* ) = 0 ) sowie ein emplace_move_to( void* ) = 0 in task_pimpl .

Live-Beispiel für den obigen Code (ohne SBO).

Löschen in einen zusammenhängenden Puffer von T

Nicht alle Typen löschen virtuelle Vererbung, Zuweisung, Platzierung neuer oder sogar Funktionszeiger.

Was das Löschen von Typen bewirkt, ist das Beschreiben einer (Gruppe von) Verhalten (en) und die Verwendung eines beliebigen Typs, der dieses Verhalten unterstützt, und das Zusammenfassen. Alle Informationen, die nicht in dieser Gruppe von Verhaltensweisen enthalten sind, werden "vergessen" oder "gelöscht".

Ein array_view nimmt seinen eingehenden Bereich oder Containertyp und löscht alles, außer dass es ein zusammenhängender Puffer von 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()) {}
};

Ein array_view nimmt jeden Container an, der .data() , der einen Zeiger auf T und eine .size() -Methode oder ein Array .size() , und löscht diesen bis zu einem Bereich mit wahlfreiem Zugriff über zusammenhängende T .

Es kann einen std::vector<T> , einen std::string<T> ein std::array<T, N> a T[37] , eine Initialisierungsliste (einschließlich {} basierter) oder etwas anderes enthalten Sie machen das, was es unterstützt (über T* x.data() und size_t x.size() ).

In diesem Fall bedeuten die Daten, die wir aus dem zu löschenden Objekt extrahieren können, zusammen mit dem Status "View", der sich nicht im Besitz des Besitzers befindet, dass wir keinen Speicher zuordnen müssen und keine benutzerdefinierten typabhängigen Funktionen schreiben müssen.

Live-Beispiel .

Eine Verbesserung wäre die Verwendung von Nicht-Mitglieder- data und einer Nicht-Mitglieder- size in einem ADL-fähigen Kontext.

Typlöschung mit std :: any löschen

In diesem Beispiel werden C ++ 14 und boost::any . In C ++ 17 können Sie stattdessen in std::any tauschen.

Die folgende Syntax lautet:

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);

das ist fast optimal.

Dieses Beispiel basiert auf der Arbeit von @dyp und @cpplearner sowie auf meinem eigenen.


Zuerst verwenden wir ein Tag, um Typen zu übergeben:

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

Diese Merkmalsklasse erhält die Signatur mit einer any_method gespeichert:

Dies erstellt einen Funktionszeigertyp und eine Factory für die Funktionszeiger, wenn eine 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 ist der Typ eines Funktionszeigers, der neben der Instanz gespeichert wird. any_method_function::operator() nimmt ein tag_t<T> und schreibt eine benutzerdefinierte Instanz des any_method_function::type , die annimmt, dass any& ein T .

Wir möchten in der Lage sein, mehr als eine Methode gleichzeitig zu löschen. Wir bündeln sie also in einem Tupel und schreiben einen Helper-Wrapper, um das Tupel pro Typ in statischen Speicher zu speichern und einen Zeiger auf sie aufzubewahren.

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

Wir könnten dies auf Fälle spezialisieren, in denen die vtable klein ist (z. B. 1 Element), und direkte Zeiger verwenden, die in diesen Fällen in der Klasse gespeichert sind, um die Effizienz zu verbessern.

Jetzt starten wir die super_any . Ich benutze super_any_t , um die Deklaration von super_any etwas zu erleichtern.

template<class...methods>
struct super_any_t;

Dadurch werden die Methoden durchsucht, die Super Any für SFINAE und bessere Fehlermeldungen unterstützt:

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> ) )
{};

Als Nächstes erstellen wir den Typ any_method . Eine any_method ist ein Pseudo-Methodenzeiger. Wir schaffen es global und const ly mit Syntax wie:

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

oder in C ++ 17:

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

Beachten Sie, dass die Verwendung eines Nicht-Lambdas die Sache haarig machen kann, da wir den Typ für einen Suchschritt verwenden. Dies kann behoben werden, würde aber dieses Beispiel länger machen, als es bereits ist. Initialisieren Sie daher immer eine beliebige Methode aus einem Lambda oder aus einem auf einem Lambda parametrisierten Typ.

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)...);
  }
};

Eine Factory-Methode, die in C ++ 17 nicht benötigt wird, glaube ich:

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

Dies ist der Augmented any . Es ist sowohl eine any , und es trägt um ein Bündel von Typ-Löschfunktionszeiger, die sich ändern , wenn das enthaltene any tut:

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;
  }  
};

Weil wir den Laden any_method s als const Objekte, macht dies zu einer machen super_any ein bisschen einfacher:

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

Testcode:

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

Live-Beispiel .

Ursprünglich hier in einer SO-Frage & Antwort gepostet (und die oben genannten Personen halfen bei der Implementierung).



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow