Sök…


Introduktion

Typ radering är en uppsättning tekniker för att skapa en typ som kan ge ett enhetligt gränssnitt till olika underliggande typer, samtidigt som den underliggande typinformationen gömmer sig från klienten. std::function<R(A...)> , som har förmågan att hålla utkallbara objekt av olika slag, är kanske det mest kända exemplet på radering av typ i C ++.

Grundläggande mekanism

Radering av typ är ett sätt att dölja typen av ett objekt från koden med det, även om det inte härstammar från en vanlig basklass. På så sätt tillhandahåller det en bro mellan staternas polymorfismvärldar (mallar; på platsen måste den exakta typen vara känd vid sammanställningstiden, men den behöver inte förklaras uppfylla ett gränssnitt vid definition) och dynamisk polymorfism (arv och virtuella funktioner; på platsen för användning behöver den exakta typen inte vara känd vid sammanställningstiden, men måste förklaras uppfylla ett gränssnitt vid definition).

Följande kod visar den grundläggande mekanismen för radering av typ.

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

På användningsplatsen behöver bara ovanstående definition vara synlig, precis som för basklasser med virtuella funktioner. Till exempel:

#include <iostream>

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

Observera att detta inte är en mall, utan en normal funktion som bara behöver deklareras i en huvudfil och kan definieras i en implementeringsfil (till skillnad från mallar, vars definition måste vara synlig på platsen för användning).

Vid definitionen av Printable behöver inget vara känt om Printable , det behöver bara anpassas till ett gränssnitt, som med mallar:

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

Vi kan nu överföra ett objekt i denna klass till den funktion som definieras ovan:

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

Radera ner till en vanlig typ med manuell vtabell

C ++ trivs med vad som kallas en vanlig typ (eller åtminstone Pseudo-Regular).

En vanlig typ är en typ som kan konstrueras och tilldelas till och tilldelas från via kopia eller flytta, kan förstöras och kan jämföras lika med. Det kan också konstrueras från några argument. Slutligen har den också stöd för några andra operationer som är mycket användbara i olika std algoritmer och containrar.

Detta är rotpapperet , men i C ++ 11 skulle man vilja lägga till std::hash stöd.

Jag kommer att använda den manuella vtable-metoden för att skriva radering här.

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

levande exempel .

En sådan vanlig typ kan användas som en nyckel för en std::map eller en std::unordered_map som accepterar allt som är regelbundet för en nyckel, som:

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

skulle i princip vara en karta från lugnande regelbundet, till allt som kan kopieras.

Till skillnad från any , min regular_type har ingen liten regular_type stöder inte heller att få tillbaka originalinformationen. Att få tillbaka den ursprungliga typen är inte svårt.

Lite objektoptimering kräver att vi lagrar en anpassad lagringsbuffert inom den regular_type och regular_type försiktigt delaren på ptr att bara förstöra objektet och inte ta bort det.

Jag skulle börja vid make_dtor_unique_ptr och lära det hur man ibland lagrar data i en buffert och sedan i högen om inget utrymme i bufferten. Det kan vara tillräckligt.

En rörlig "std :: funktion"

std::function raderas till några få operationer. En av sakerna det kräver är att det lagrade värdet kan kopieras.

Detta orsakar problem i några få sammanhang, som lambdas som lagrar unika ptrs. Om du använder std::function i ett sammanhang där kopiering inte spelar någon roll, som en trådpool där du skickar uppgifter till trådar, kan detta krav lägga till omkostnader.

I synnerhet är std::packaged_task<Sig> ett kallbart objekt som är rörligt. Du kan lagra en std::packaged_task<R(Args...)> i en std::packaged_task<void(Args...)> , men det är ett ganska tungt och otydligt sätt att skapa en rörelse klassbar typ-raderingsklass.

Således task . Detta visar hur du kan skriva en enkel std::function . Jag utelämnade kopieringskonstruktören (som skulle innebära att lägga till en clone till details::task_pimpl<...> också).

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

För att göra det här biblioteket värt, vill du lägga till en liten buffertoptimering, så att det inte lagrar alla avräknbara på högen.

Att lägga till SBO kräver en icke-standarduppgift task(task&&) , en del std::aligned_storage_t i klassen, en m_pImpl unique_ptr med en deleter som kan ställas in för förstörande (och inte återlämna minnet till högen) och en emplace_move_to( void* ) = 0 i task_pimpl .

levande exempel på ovanstående kod (utan SBO).

Radera ner till en sammanhängande buffert av T

Inte alla typer av radering innebär virtuell arv, tilldelningar, ny placering eller till och med funktionspekare.

Det som gör att radering av typ är radering är att den beskriver (uppsättning) beteenden och tar alla typer som stöder det beteendet och lindar det. All information som inte finns i den uppsättningen beteenden är "glömd" eller "raderad".

En array_view tar sitt inkommande räckvidd eller behållartyp och raderar allt förutom att det är en sammanhängande buffert av 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()) {}
};

en array_view tar alla behållare som stöder .data() returnerar en pekare till T och en .size() -metod eller en matris och raderar den ned till att vara ett slumpmässigt åtkomstintervall över sammanhängande T er.

Det kan ta en std::vector<T> , en std::string<T> en std::array<T, N> a T[37] , en initialiseringslista (inklusive {} baserade) eller något annat du gör det som stöder det (via T* x.data() och size_t x.size() ).

I det här fallet betyder de data vi kan hämta ut från det vi raderar, tillsammans med vårt "visa" icke-ägda tillstånd, att vi inte behöver tilldela minne eller skriva anpassade typberoende funktioner.

Levande exempel .

En förbättring skulle vara att använda en data och en tredje size i en ADL-aktiverad sammanhang.

Skriv radering av radering med std :: valfri

Detta exempel använder C ++ 14 och boost::any . I C ++ 17 kan du byta in std::any istället.

Syntaxen vi slutar med är:

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

vilket är nästan optimalt.

Detta exempel är baserat på arbete av @dyp och @cpplearner såväl som mitt eget.


Först använder vi en tagg för att skicka runt typer:

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

Den här egenskapsklassen får signaturen lagrad med en any_method :

Detta skapar en funktionspekartyp och en fabrik för nämnda funktionspekare, givet en 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 är typen av en funktionspekare som vi lagrar vid sidan av instansen. any_method_function::operator() tar en tag_t<T> och skriver en anpassad instans av any_method_function::type som antar att any& kommer att bli en T

Vi vill kunna radera mer än en metod i taget. Så vi buntar dem upp i en tupel och skriver en hjälperomslag för att fästa tupeln i statisk lagring per typ och hålla en pekare till dem.

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

Vi kan specialisera detta för fall där vtabellen är liten (till exempel 1 artikel) och använda direkta pekare som är lagrade i klassen i dessa fall för effektivitet.

Nu börjar vi super_any . Jag använder super_any_t att göra deklarationen om super_any lite enklare.

template<class...methods>
struct super_any_t;

Detta söker efter de metoder som super super stöder för SFINAE och bättre felmeddelanden:

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

Nästa skapar any_method typen any_method . En any_method är en pseudo-metod-pekare. Vi skapar det globalt och const ly använder syntax som:

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

eller i C ++ 17:

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

Observera att användning av en icke-lambda kan göra saker håriga, eftersom vi använder typen för ett uppslagsteg. Detta kan åtgärdas, men skulle göra detta exempel längre än det redan är. Så initialisera alltid en valfri metod från en lambda, eller från en typ som är parametriserad på en 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)...);
  }
};

En fabriksmetod som inte behövs i C ++ 17 tror jag:

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

Detta är den förstärkta any . Det är både ett any , och det bär runt en bunt av typ raderingsfunktion pekare som förändring när innehöll any gör:

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

Eftersom vi lagrar any_method s som const objekt, gör detta att göra en super_any lite enklare:

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

Testkod:

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

levande exempel .

Ursprungligen postade här i en SO självfråga och svar (och personer som noterats ovan hjälpte till med implementeringen).



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow