수색…


소개

형식 지우기는 클라이언트에서 기본 형식 정보를 숨기면서 다양한 기본 형식에 대한 일관된 인터페이스를 제공 할 수있는 형식을 만드는 기술 집합입니다. 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);

수동 vtable로 일반 유형으로 지우기

C ++은 Regular 타입 (또는 적어도 Pseudo-Regular)으로 알려져 있습니다.

정규 유형은 복사 또는 이동을 통해 생성 및 할당되고 배정되고 파괴 될 수 있고 동일하게 비교 될 수있는 유형입니다. 인수없이 구성 할 수도 있습니다. 마지막으로, 다양한 std 알고리즘과 컨테이너에서 매우 유용한 몇 가지 다른 연산을 지원합니다.

이것이 루트 페이퍼 이지만 C ++ 11에서는 std::hash 지원을 추가하려고합니다.

여기에 수동으로 vtable 접근 방식을 사용하여 삭제합니다.

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>

기본적으로 anothing에서 copyable에 이르는지도 일 것입니다.

any 와는 달리, regular_type 은 작은 객체 최적화를 수행하지 않으며 원본 데이터를 다시 가져 오는 것을 지원하지 않습니다. 원래 유형을 다시 얻는 것은 어렵지 않습니다.

소규모 개체 최적화를 위해서는 regular_type 내에 정렬 된 저장소 버퍼를 저장하고 ptr 삭제자를 조심스럽게 조정하여 개체 만 삭제하고 삭제하지 않도록해야합니다.

make_dtor_unique_ptr 에서 시작하여 버퍼에 데이터를 저장하는 방법과 버퍼가없는 경우 힙에 데이터를 저장하는 방법을 가르쳐줍니다. 충분할 수도 있습니다.

이동 전용`std :: function`

std::function type은 몇 가지 연산을 지운다. 필요한 것 중 하나는 저장된 값을 복사 할 수 있다는 것입니다.

이것은 고유 한 ptrs를 저장하는 lambdas와 같은 몇 가지 상황에서 문제를 일으 킵니다. 복사 작업이 중요하지 않은 컨텍스트에서 스레드에 작업을 디스패치하는 스레드 풀과 같이 std::function 를 사용하는 경우이 요구 사항으로 인해 오버 헤드가 추가 될 수 있습니다.

특히, std::packaged_task<Sig> 는 이동 전용 인 호출 가능 객체입니다. std::packaged_task<void(Args...)> std::packaged_task<R(Args...)> 를 저장할 수 있지만 꽤 무겁고 가려운 방법으로 이동 전용을 만들 수 있습니다 호출 가능 유형 삭제 클래스.

따라서 task . 이것은 간단한 std::function 유형을 작성하는 방법을 보여줍니다. 복사 생성자를 생략했습니다 ( 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 , destroy 전용으로 설정할 수있는 deleter가있는 m_pImpl unique_ptr (메모리를 힙으로 반환하지 않음) 및 emplace_move_to( void* ) = 0 task_pimpl 에서 emplace_move_to( void* ) = 0 으로 설정하십시오.

위 코드의 라이브 예 (SBO 없음).

T의 인접 버퍼로 지우기

모든 타입 삭제가 가상 상속, 할당, 새로운 배치 또는 함수 포인터를 포함하는 것은 아닙니다.

타입 소거 타입 소거를 만드는 이유는 (일련의) 행동을 기술하고, 그 행동을 지원하고 그것을 감싸는 모든 타입을 취한다는 것입니다. 해당 행동 집합에 포함되지 않은 모든 정보는 "잊혀진"또는 "지워짐"입니다.

array_view 는 들어오는 범위 나 컨테이너 타입을 취해 T 의 인접한 버퍼라는 사실을 제외하고 모든 것을 지 웁니다.

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

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

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

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

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

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

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

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

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

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

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

array_viewT.size() 메소드 또는 배열에 대한 포인터를 리턴하는 .data() 를 지원하는 컨테이너를 취해 연속적인 T 에 대해 임의 액세스 범위로 지운다.

이것은 취할 수 std::vector<T> 하는 std::string<T> std::array<T, N> T[37] , 이니셜 라이저 (체크리스트 {} 기초 것), 또는 어떤 다른 ( T* x.data()size_t x.size() ) 지원합니다.

이 경우 우리가 지우는 것에서 추출 할 수있는 데이터는 우리의 "view"비 소유 상태와 함께 메모리를 할당하거나 사용자 정의 유형 종속 함수를 작성할 필요가 없다는 것을 의미합니다.

실례 예 .

ADL 사용 가능 컨텍스트에서 비 멤버 data 와 비 멤버 size 를 사용하는 것이 개선되었습니다.

std :: any를 사용하여 삭제 유형 삭제

이 예제는 C ++ 14와 boost::any . C ++ 17에서는 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 주어지면, 함수 포인터 타입과 상기 함수 포인터를위한 팩토리를 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::operator()tag_t<T> any_method_function::type 의 사용자 정의 인스턴스를 작성합니다.이 인스턴스의 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 );
  }
};

vtable이 작은 경우 (예 : 1 개 항목)에이를 전문화 할 수 있으며 효율성을 위해 클래스에 저장된 직접 포인터를 사용합니다.

이제 우리는 super_any 시작합니다. 내가 사용 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; };

non-lambda를 사용하면 룩을 단계적으로 사용하기 때문에 털이 많은 것을 만들 수 있습니다. 이 문제는 해결할 수 있지만이 예제는 이미 존재하는 것보다 길게 만듭니다. 그래서 항상 람다 또는 람다에 parametarized 형식에서 모든 메서드를 초기화하십시오.

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 가 변할 때마다 바뀌는 type-erasure 함수 포인터 묶음을 전달 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_methodconst 객체로 저장하기 때문에 이렇게하면 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);
}

라이브 예제 .

원래 SO 자체 질문 및 답변 (및 위에 언급 된 사람들은 구현에 도움이 됨)에 게시 되었습니다 .



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow