Поиск…


Вступление

Тип erasure - это набор методов для создания типа, который может обеспечить единый интерфейс для различных базовых типов, при этом скрывая информацию о базовом типе от клиента. 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);

Стирание до типа Regular с помощью ручной таблицы vtable

C ++ преуспевает в том, что называется регулярным типом (или, по крайней мере, 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>

была бы в основном картой из обычного органа, к чему-либо, скопированному.

В отличие от any , мой regular_type не оптимизирует мелкие объекты и не поддерживает возврат исходных данных. Получение первоначального типа обратно не сложно.

Небольшая оптимизация объектов требует , чтобы мы храним выровненный буфер для хранения внутри regular_type , и тщательно настроить Deleter в ptr только уничтожить объект , а не удалять его.

Я бы начал с make_dtor_unique_ptr и научил его, как иногда хранить данные в буфере, а затем в куче, если в буфере нет места. Этого может быть достаточно.

Только для перемещения `std :: function`

std::function type стирает до нескольких операций. Одна из вещей, которую он требует, заключается в том, что сохраненное значение можно копировать.

Это вызывает проблемы в нескольких контекстах, таких как lambdas, хранящие уникальные ptrs. Если вы используете 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 с Deleter , который может быть установлен , чтобы уничтожить только (и не возвращать память в куче), и emplace_move_to( void* ) = 0 в task_pimpl .

живой пример вышеуказанного кода (без 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_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 не члена в контексте с поддержкой ADL.

Тип стирания стирания стилей с помощью 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 :

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

Обратите внимание, что использование не-лямбда может сделать вещи волосатыми, поскольку мы используем тип для этапа поиска. Это можно исправить, но сделать этот пример длиннее, чем он есть. Поэтому всегда инициализируйте любой метод из лямбда или из типа, параметризованного на лямбда.

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 делают:

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 s как объекты 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);
}

живой пример .

Первоначально опубликовано здесь в SO вопрос и ответ (и люди, отмеченные выше, помогли с реализацией).



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow