Zoeken…


Invoering

Type wissen is een set technieken voor het maken van een type dat een uniforme interface voor verschillende onderliggende typen kan bieden, terwijl de onderliggende type-informatie voor de klant wordt verborgen. std::function<R(A...)> , die de mogelijkheid heeft om afroepbare objecten van verschillende typen te bevatten, is misschien het bekendste voorbeeld van type wissen in C ++.

Basismechanisme

Type wissen is een manier om het type van een object te verbergen voor code die het gebruikt, ook al is het niet afgeleid van een algemene basisklasse. Daarbij vormt het een brug tussen de werelden van statisch polymorfisme (sjablonen; op de plaats van gebruik moet het exacte type bekend zijn tijdens het compileren, maar het hoeft niet verklaard te worden dat het bij de definitie overeenkomt met een interface) en dynamisch polymorfisme (overerving en virtuele functies; op de plaats van gebruik hoeft het exacte type niet bekend te zijn tijdens het compileren, maar moet bij de definitie worden verklaard dat het voldoet aan een interface).

De volgende code toont het basismechanisme voor het wissen van tekst.

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

Op de gebruikssite hoeft alleen de bovenstaande definitie zichtbaar te zijn, net als bij basisklassen met virtuele functies. Bijvoorbeeld:

#include <iostream>

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

Merk op dat dit geen sjabloon is, maar een normale functie die alleen in een header-bestand moet worden gedeclareerd en kan worden gedefinieerd in een implementatiebestand (in tegenstelling tot sjablonen, waarvan de definitie zichtbaar moet zijn op de plaats van gebruik).

Bij de definitie van het concrete type hoeft er niets bekend te zijn over Printable , het hoeft alleen maar te voldoen aan een interface, zoals bij sjablonen:

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

We kunnen nu een object van deze klasse doorgeven aan de hierboven gedefinieerde functie:

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

Wissen naar een regulier type met handmatige vtable

C ++ gedijt op wat bekend staat als een Regular-type (of op zijn minst Pseudo-Regular).

Een regulier type is een type dat kan worden geconstrueerd en toegewezen aan en toegewezen via kopiëren of verplaatsen, kan worden vernietigd en kan worden vergeleken met gelijk aan. Het kan ook zonder argumenten worden opgebouwd. Tot slot, het heeft ook ondersteuning voor een aantal andere activiteiten die zeer nuttig in verschillende zijn std algoritmen en containers.

Dit is het root-paper , maar zou in C ++ 11 ondersteuning voor std::hash willen toevoegen.

Ik zal de handmatige vtable-aanpak gebruiken om hier het type wissen te typen.

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 voorbeeld .

Zo'n regulier type kan worden gebruikt als een sleutel voor een std::map of een std::unordered_map die iets std::unordered_map accepteert voor een sleutel, zoals:

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

zou in feite een kaart zijn van alles wat normaal is, naar alles wat kan worden gekopieerd.

In tegenstelling tot any , biedt mijn regular_type geen optimalisatie van kleine objecten, noch ondersteunt het om de oorspronkelijke gegevens terug te krijgen. Het oorspronkelijke type terughalen is niet moeilijk.

Optimalisatie van kleine objecten vereist dat we een uitgelijnde opslagbuffer opslaan binnen het regular_type , en zorgvuldig de deleter van de ptr aanpassen om het object alleen te vernietigen en niet te verwijderen.

Ik zou beginnen met make_dtor_unique_ptr en het leren hoe de gegevens soms in een buffer kunnen worden opgeslagen en vervolgens in de heap als er geen ruimte in de buffer is. Dat kan voldoende zijn.

Een alleen-verplaatsen `std :: functie`

std::function wist tot een paar bewerkingen. Het vereist onder meer dat de opgeslagen waarde kan worden gekopieerd.

Dit veroorzaakt problemen in een paar contexten, zoals lambdas die unieke ptrs opslaat. Als u de std::function in een context waarin kopiëren niet uitmaakt, zoals een threadpool waar u taken naar threads verzendt, kan deze vereiste overhead toevoegen.

In het bijzonder is std::packaged_task<Sig> een opvraagbaar object dat alleen kan worden verplaatst. Je kunt een std::packaged_task<R(Args...)> in een std::packaged_task<void(Args...)> , maar dat is een behoorlijk zware en obscure manier om een alleen-verplaatsen te maken opvraagbare type-wisklasse.

Dus de task . Dit laat zien hoe je een eenvoudig std::function kunt schrijven. Ik weggelaten de kopie constructor (wat zou inhouden het toevoegen van een clone methode om details::task_pimpl<...> ook).

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

Om deze bibliotheek de moeite waard te maken, zou je een kleine bufferoptimalisatie willen toevoegen, zodat het niet elke opvraagbare opslag opslaat.

Het toevoegen van SBO vereist een niet-standaardtaak task(task&&) , een aantal std::aligned_storage_t binnen de klasse, een m_pImpl unique_ptr met een deleter die kan worden ingesteld op alleen-vernietigen (en het geheugen niet terugzetten op de heap), en een emplace_move_to( void* ) = 0 in de task_pimpl .

live voorbeeld van de bovenstaande code (zonder SBO).

Wissen naar een aangrenzende buffer van T

Niet alle typen wissen omvat virtuele overerving, toewijzingen, nieuwe plaatsing of zelfs functieaanwijzingen.

Wat het wissen van het type maakt, is dat het een (set van) gedrag (en) beschrijft en elk type dat dat gedrag ondersteunt neemt en afrondt. Alle informatie die niet in die set gedragingen voorkomt, wordt "vergeten" of "gewist".

Een array_view neemt het inkomende bereik of containertype en wist alles behalve het feit dat het een aaneengesloten buffer van 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()) {}
};

een array_view neemt elke container die .data() ondersteunt en retourneert een pointer naar T en een .size() methode, of een array, en wist het tot een willekeurig toegankelijk bereik over aaneengesloten T 's.

Het kan een std::vector<T> , een std::string<T> een std::array<T, N> een T[37] , een initialisatielijst (inclusief {} gebaseerde) of iets anders bevatten je maakt het op dat het ondersteunt (via T* x.data() en size_t x.size() ).

In dit geval betekent de data die we kunnen extraheren van het ding dat we wissen, samen met onze "view" niet-eigendomsstatus, dat we geen geheugen hoeven toe te wijzen of aangepaste type-afhankelijke functies te schrijven.

Live voorbeeld .

Een verbetering zou zijn om een derde te gebruiken data en een derde size in een context-ADL ingeschakeld.

Type wissen type wissen met std :: any

Dit voorbeeld gebruikt C ++ 14 en boost::any . In C ++ 17 kun je in plaats daarvan std::any omwisselen.

De syntaxis waarmee we eindigen is:

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

wat bijna optimaal is.

Dit voorbeeld is gebaseerd op werk van @dyp en @cpplearner evenals het mijne.


Eerst gebruiken we een tag om types door te geven:

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

Deze eigenschapsklasse krijgt de handtekening opgeslagen met een any_method :

Dit creëert een type functiepointer en een fabriek voor genoemde functiepointers, gegeven een 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 is het type functiepointer dat we naast de instantie opslaan. any_method_function::operator() neemt een tag_t<T> en schrijft een aangepast exemplaar van het any_method_function::type dat ervan uitgaat any& een T .

We willen meerdere typen tegelijk kunnen wissen. Dus bundelen we ze in een tuple, en schrijven een helperwikkelaar om de tuple per type in statische opslag te steken en een wijzer naar hen te handhaven.

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

We zouden dit kunnen specialiseren in gevallen waarin de vtable klein is (bijvoorbeeld 1 item), en in die gevallen directe aanwijzers gebruiken die in die klasse zijn opgeslagen voor efficiëntie.

Nu beginnen we de super_any . Ik gebruik super_any_t om de aangifte van super_any een beetje eenvoudiger te maken.

template<class...methods>
struct super_any_t;

Dit doorzoekt de methoden die de super ondersteunt voor SFINAE en betere foutmeldingen:

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

Vervolgens maken we het any_method type. Een any_method is een pseudo-methode-pointer. We maken het wereldwijd en const met behulp van syntaxis zoals:

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

of in C ++ 17:

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

Merk op dat het gebruik van een niet-lambda dingen harig kan maken, omdat we het type gebruiken voor een opzoekstap. Dit kan worden opgelost, maar zou dit voorbeeld langer maken dan het al is. Dus initialiseer altijd een methode van een lambda of van een type dat op een lambda is ingesteld.

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

Een fabrieksmethode, niet nodig in C ++ 17 Ik geloof:

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

Dit vergroot de any . Het is zowel een any , en draagt rond een bundel van type wissen functieverwijzingen die verandering wanneer de gegevens any doet:

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

Omdat we de any_method s opslaan als const objecten, maakt dit het super_any een beetje eenvoudiger:

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

Test code:

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 voorbeeld .

Oorspronkelijk hier gepost in een SO self question & answer (en de hierboven genoemde mensen hebben geholpen bij de implementatie).



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow