खोज…


परिचय

टाइप इरेज़र एक प्रकार बनाने के लिए तकनीकों का एक सेट है जो क्लाइंट से अंतर्निहित प्रकार की जानकारी छिपाते हुए विभिन्न अंतर्निहित प्रकारों को एक समान इंटरफ़ेस प्रदान कर सकता है। std::function<R(A...)> , जिसमें विभिन्न प्रकार की कॉल करने योग्य वस्तुओं को रखने की क्षमता है, शायद C ++ में टाइप इरेज़र का सबसे अच्छा ज्ञात उदाहरण है।

बुनियादी तंत्र

टाइप इरेज़र कोड का उपयोग करके किसी ऑब्जेक्ट के प्रकार को छिपाने का एक तरीका है, भले ही यह एक सामान्य आधार वर्ग से प्राप्त न हो। ऐसा करने पर, यह स्थैतिक बहुरूपता की दुनिया के बीच एक सेतु प्रदान करता है (टेम्पलेट्स; उपयोग की जगह पर, संकलन समय पर सटीक प्रकार ज्ञात होना चाहिए, लेकिन इसे परिभाषा में इंटरफ़ेस के अनुरूप घोषित नहीं किया जाना चाहिए) और गतिशील बहुरूपता (इनहेरिटेंस और वर्चुअल फ़ंक्शंस; उपयोग की जगह पर, संकलन समय पर सटीक प्रकार की आवश्यकता नहीं होती है, लेकिन परिभाषा में एक इंटरफ़ेस के अनुरूप घोषित किया जाना चाहिए)।

निम्नलिखित कोड प्रकार के क्षरण के बुनियादी तंत्र को दर्शाता है।

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

उपयोग स्थल पर, केवल उपर्युक्त परिभाषा को देखने की आवश्यकता है, जैसे आभासी कार्यों के साथ आधार कक्षाएं। उदाहरण के लिए:

#include <iostream>

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

ध्यान दें कि यह एक टेम्पलेट नहीं है, लेकिन एक सामान्य फ़ंक्शन जिसे केवल हेडर फ़ाइल में घोषित करने की आवश्यकता होती है, और इसे एक कार्यान्वयन फ़ाइल में परिभाषित किया जा सकता है (टेम्पलेट के विपरीत, जिसकी परिभाषा उपयोग के स्थान पर दिखाई देनी चाहिए)।

कंक्रीट प्रकार की परिभाषा में, Printable बारे में कुछ भी जानने की आवश्यकता नहीं है, इसे बस एक इंटरफ़ेस के अनुरूप होने की आवश्यकता है, जैसा कि टेम्पलेट्स के साथ:

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

अब हम इस वर्ग के एक ऑब्जेक्ट को ऊपर परिभाषित फ़ंक्शन में पास कर सकते हैं:

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

मैन्युअल रूप से व्यवहार्य के साथ एक नियमित प्रकार के लिए नीचे

C ++ एक नियमित प्रकार (या कम से कम छद्म-नियमित) के रूप में जाना जाता है पर पनपती है।

एक नियमित प्रकार एक प्रकार है जिसे कॉपी या चाल के माध्यम से निर्माण और सौंपा और सौंपा जा सकता है, नष्ट किया जा सकता है, और इसकी तुलना बराबर की जा सकती है। इसका निर्माण बिना किसी तर्क के भी किया जा सकता है। अंत में, इसमें कुछ अन्य परिचालनों के लिए भी समर्थन है जो विभिन्न std एल्गोरिदम और कंटेनरों में अत्यधिक उपयोगी हैं।

यह रूट पेपर है , लेकिन C ++ 11 में std::hash support जोड़ना चाहते हैं।

मैं यहाँ इरेज़र टाइप करने के लिए मैन्युअल वाइबेट अप्रोच का उपयोग करूँगा।

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

जीवंत उदाहरण

इस तरह के एक नियमित प्रकार का उपयोग एक std::map या std::unordered_map लिए एक कुंजी के रूप में किया जा सकता है, जो एक कुंजी के लिए कुछ भी नियमित स्वीकार करता है , जैसे:

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

मूल रूप से सुखद नियमित से एक नक्शा होगा, कुछ भी प्रतिलिपि करने योग्य।

any विपरीत, मेरा regular_type कोई छोटी वस्तु अनुकूलन नहीं करता है और न ही यह मूल डेटा को वापस पाने का समर्थन करता है। मूल प्रकार वापस पाना कठिन नहीं है।

छोटे ऑब्जेक्ट ऑप्टिमाइज़ेशन के लिए आवश्यक है कि हम regular_type भीतर एक संरेखित स्टोरेज बफर स्टोर करें, और ध्यान से केवल ऑब्जेक्ट को नष्ट न करने के लिए ptr के डीलेटर को ट्वीक करें और इसे डिलीट न करें।

मैं make_dtor_unique_ptr पर शुरू make_dtor_unique_ptr और सिखाऊंगा कि कैसे कभी-कभी डेटा को बफर में स्टोर किया जाए, और फिर ढेर में अगर बफर में कोई जगह नहीं है। यह पर्याप्त हो सकता है।

एक चाल-केवल `एसटीडी :: फ़ंक्शन`

std::function प्रकार कुछ ऑपरेशनों को मिटा देता है। इनमें से एक चीज की आवश्यकता है कि संग्रहीत मूल्य प्रतिलिपि योग्य हो।

यह कुछ संदर्भों में समस्याओं का कारण बनता है, जैसे कि लैम्ब्डा अद्वितीय पीटीएस भंडारण। यदि आप std::function का उपयोग किसी ऐसे संदर्भ में कर रहे हैं जहाँ कॉपी करना कोई मायने नहीं रखता है, जैसे थ्रेड पूल जहाँ आप थ्रेड को कार्य भेजते हैं, यह आवश्यकता ओवरहेड को जोड़ सकती है।

विशेष रूप से, std::packaged_task<Sig> एक कॉल करने योग्य वस्तु है जो केवल चाल है। आप एक std::packaged_task<R(Args...)> को std::packaged_task<void(Args...)> संग्रहीत कर सकते हैं, लेकिन यह एक बहुत भारी वजन और अस्पष्ट तरीके से केवल एक कदम बनाने के लिए है कॉल करने योग्य प्रकार-मिटा वर्ग।

इस प्रकार task । यह दर्शाता है कि आप एक साधारण std::function प्रकार कैसे लिख सकते हैं। मैंने कॉपी कंस्ट्रक्टर को छोड़ दिया (जिसमें details::task_pimpl<...> लिए एक clone विधि जोड़ना होगा 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());
  }
};

इस लाइब्रेरी को योग्य बनाने के लिए, आप एक छोटे बफर ऑप्टिमाइज़ेशन में जोड़ना चाहते हैं, इसलिए यह ढेर पर प्रत्येक कॉल करने योग्य स्टोर नहीं करता है।

SBO को जोड़ने के लिए एक गैर-डिफ़ॉल्ट task(task&&) आवश्यकता होगी, वर्ग के भीतर कुछ std::aligned_storage_t , एक m_pImpl unique_ptr m_pImpl unique_ptr साथ नष्ट करने के लिए सेट किया जा सकता है (और ढेर को स्मृति वापस नहीं), और एक emplace_move_to( void* ) = 0 task_pimpl में emplace_move_to( void* ) = 0

उपरोक्त कोड का लाइव उदाहरण (बिना SBO के)।

टी के एक सन्निहित बफर के नीचे मिटा

सभी प्रकार के क्षरण में आभासी वंशानुक्रम, आवंटन, प्लेसमेंट नया या फ़ंक्शन पॉइंट शामिल नहीं होते हैं।

टाइप इरेज़र टाइप इरेज़र क्या होता है, यह एक (व्यवहार) सेट का वर्णन करता है, और किसी भी प्रकार को लेता है जो उस व्यवहार का समर्थन करता है और इसे लपेटता है। व्यवहार के उस सेट में नहीं है कि सभी जानकारी "भूल" या "मिट" है।

एक array_view अपनी आने वाली रेंज या कंटेनर प्रकार लेता है और इस तथ्य को छोड़कर सब कुछ मिटा देता है यह T एक array_view बफर है।

// 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 किसी भी कंटेनर को लेता है जो array_view .data() को T और एक .size() विधि या एक सरणी के लिए एक पॉइंटर लौटाता है, और इसे सन्निहित T s पर रैंडम-एक्सेस रेंज होने के लिए मिटा देता है।

यह एक std::vector<T> , एक std::string<T> a std::array<T, N> a T[37] , एक इनिशलाइज़र सूची ( {} आधारित वाले सहित) ले सकता है, या कुछ और ले सकता है आप इसका समर्थन करते हैं ( T* x.data() और size_t x.size() ) के माध्यम से।

इस मामले में, डेटा हम उस चीज़ से निकाल सकते हैं जिसे हम मिटा रहे हैं, साथ में हमारे "दृश्य" गैर-मालिक राज्य के साथ है, इसका मतलब है कि हमें मेमोरी आवंटित करने या कस्टम प्रकार-निर्भर कार्यों को लिखने की आवश्यकता नहीं है।

जीवंत उदाहरण

एक एडीएल-सक्षम संदर्भ में गैर-सदस्य data और गैर-सदस्य size का उपयोग करने के लिए एक सुधार होगा।

Std के साथ इरेज़िंग टाइप इरेज़र :: कोई भी

यह उदाहरण C ++ 14 और boost::any उपयोग करता है। C ++ 17 में आप std::any में स्वैप कर सकते हैं std::any बजाय।

जो सिंटैक्स हम समाप्त करते हैं वह है:

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

जो लगभग इष्टतम है।

यह उदाहरण @dyp और @cpplearner के साथ-साथ मेरे स्वयं के काम से आधारित है।


पहले हम टाइप करने के लिए टैग का उपयोग करते हैं:

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

इस विशेषता वर्ग को हस्ताक्षर को any_method साथ संग्रहीत किया जाता है:

यह एक फंक्शन पॉइंटर टाइप बनाता है, और कहा गया फंक्शन पॉइंटर्स के लिए एक फैक्ट्री, जिसे any_method दिया 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 एक फ़ंक्शन पॉइंटर का any_method_function::type है जिसे हम उदाहरण के साथ संगृहीत करेंगे। any_method_function::operator() एक tag_t<T> लेता है और tag_t<T> एक कस्टम उदाहरण लिखता है any_method_function::type जो any& मानता है any& एक T होने जा रहा है।

हम एक बार में एक से अधिक तरीकों को टाइप-मिटाने में सक्षम होना चाहते हैं। इसलिए हम उन्हें एक ट्यूपल में बंडल करते हैं, और एक सहायक आवरण को प्रति-प्रकार के आधार पर स्थिर भंडारण में टपल को चिपकाने और उन्हें एक पॉइंटर बनाए रखने के लिए लिखते हैं।

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

हम इसे ऐसे मामलों के लिए विशेषज्ञ बना सकते हैं जहां व्यवहार्य छोटा है (उदाहरण के लिए, 1 आइटम), और दक्षता के लिए उन मामलों में कक्षा में संग्रहीत प्रत्यक्ष बिंदुओं का उपयोग करें।

अब हम super_any शुरू करते हैं। मैं super_any_t का उपयोग super_any_t की घोषणा को थोड़ा आसान बनाने के लिए करता super_any

template<class...methods>
struct super_any_t;

यह उन तरीकों को खोजता है जो सुपर किसी भी SFINAE और बेहतर त्रुटि संदेशों के लिए समर्थन करता है:

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

आगे हम any_method टाइप बनाते हैं। एक any_method एक छद्म विधि-सूचक है। हम इसे विश्व स्तर पर बनाते हैं और वाक्य रचना का उपयोग करते हुए const लाइक करते हैं:

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

या C ++ 17 में:

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

ध्यान दें कि नॉन-लैम्ब्डा का उपयोग करने से बाल रूखे हो सकते हैं, क्योंकि हम लुकअप स्टेप के लिए टाइप का उपयोग करते हैं। यह तय किया जा सकता है, लेकिन यह उदाहरण पहले की तुलना में अधिक लंबा बना देगा। इसलिए हमेशा किसी भी विधि को एक लैम्ब्डा से, या एक प्रकार से लंबराइज्ड एक लैम्ब्डा से शुरू करें।

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

एक कारखाना विधि, C ++ 17 में आवश्यक नहीं है मेरा मानना है:

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

यह any संवर्धित है। यह any एक दोनों है, और यह टाइप-इरेज़र फ़ंक्शन पॉइंटर्स के एक बंडल के चारों ओर ले जाता है जो कि जब any कोई any करता है, उसे बदल देता है:

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

क्योंकि हम स्टोर any_method के रूप में एस const वस्तुओं, यह एक बनाने बनाता super_any थोड़ा आसान:

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

टेस्ट कोड:

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

जीवंत उदाहरण

मूल रूप से एक एसओ स्व प्रश्न और उत्तर में यहां पोस्ट किया गया है (और उपरोक्त लोगों ने कार्यान्वयन के साथ मदद की)।



Modified text is an extract of the original Stack Overflow Documentation
के तहत लाइसेंस प्राप्त है CC BY-SA 3.0
से संबद्ध नहीं है Stack Overflow