Тип стирающий, тип стирающий, `любые` вопросы?

Итак, предположим, что я хочу набрать стирание, используя стирание типа.

Я могу создать псевдо-методы для вариантов, которые включают естественный:

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

std::variant<A,B,C> var = // create a variant of type A B or C

(var->*print)(std::cout); // print it out without knowing what it is

Мой вопрос, как я могу расширить это доstd::any?

Это не может быть сделано "в сыром". Но в тот момент, когда мы назначаем / построитьstd::any у нас есть информация о типе, нам нужно.

Таким образом, в теории, дополненнаяany:

template<class...OperationsToTypeErase>
struct super_any {
  std::any data;
  // or some transformation of OperationsToTypeErase?
  std::tuple<OperationsToTypeErase...> operations;
  // ?? what for ctor/assign/etc?
};

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

В идеале это было бы так же кратко в использовании, как вариант варианта.

template<class...Ops, class Op,
  // SFINAE filter that an op matches:
  std::enable_if_t< std::disjunction< std::is_same<Ops, Op>... >{}, int>* =nullptr
>
decltype(auto) operator->*( super_any<Ops...>& a, any_method<Op> ) {
  return std::get<Op>(a.operations)(a.data);
}

Теперь я могу оставить этотипи все же разумно использовать лямбда-синтаксис для простоты?

В идеале я хочу:

any_method<void(std::ostream&)> print =
  [](auto&& self, auto&& os){ os << self; };

using printable_any = make_super_any<&print>;

printable_any bob = 7; // sets up the printing data attached to the any

int main() {
  (bob->*print)(std::cout); // prints 7
  bob = 3.14159;
  (bob->*print)(std::cout); // prints 3.14159
}

или похожий синтаксис. Это невозможно? Неосуществимым? Легко?

 Johannes Schaub - litb09 авг. 2016 г., 09:58
Когда я прочитал пример документации с вариантом и оператором -> *, я понял, что это ты, не глядя на имя
 Yakk - Adam Nevraumont09 авг. 2016 г., 01:53
@dypИтеративное улучшение вашего кода, сложенного с моим (в настоящее время удаленным) ответом ниже.
 Jonathan Leffler22 сент. 2017 г., 18:17
Не могли бы вы обновить вопрос, чтобы избежать ссылок на SO Docs? Пожалуйста, отметьте этот комментарий «больше не нужен», когда вы закончите. (УвидетьУдаление документации: репутация, архив и ссылки если вы не знаете, что происходит и что делать. Спасибо.
 dyp08 авг. 2016 г., 22:46
Я не уверен, что я собрал все части вместе, но воточень набросок:coliru.stacked-crooked.com/a/2ab8d7e41d24e616
 milleniumbug10 авг. 2016 г., 17:33
Если вам разрешено использовать Boost, вы можете попробовать Boost.TypeErasure (пример использования)
 dyp08 авг. 2016 г., 22:57
Слегка улучшен с перегрузкой оператора:coliru.stacked-crooked.com/a/23a25da83c5ba11d
 TartanLlama08 авг. 2016 г., 20:47
Тангенциально связанные; против какой реализации вы тестируете? У кого-нибудь из основных stdlibs есть легкодоступные версии?
 Yakk - Adam Nevraumont08 авг. 2016 г., 22:06
@ ТартанЛлама Тестирование? Вы имеете в виду это или вариант один? Я не Сделал это противboost::variant только сейчас и нашел опечатку. По большей части, за исключением небольших различий и уродства синтаксиса, которые очищает C ++ 17, тестированиеstd::any решения противboost::any будет достаточно, чтобы быть уверенным, по крайней мере, на онлайн-компиляторе.
 Yakk - Adam Nevraumont10 авг. 2016 г., 17:58
@milleniumbug Я не знаю, как получить пятно->*dofoo или похожий синтаксис с Boost.TypeErasure, но я не знаком с ним.
 Yakk - Adam Nevraumont08 авг. 2016 г., 22:08
@NirFriedman Вы говорите о том, чтобы попробовать что-то подобное, где вы хотите иметь возможность (скажем) напечатать что-нибудь вообще и в конечном итоге использоватьстирание типа сделать это ... или вы говорите о стирании типа стирание типасам и столкнуться с проблемами тогда? Виртуальный недостаток по шаблону по большей части решается удалением типа, если вы не хотите многократную диспетчеризациюдва неограниченные множества типов аргументов вотчетливый пятна в коде.
 Yakk - Adam Nevraumont09 авг. 2016 г., 01:26
@ Да, да, это доказательство концепции. Не разделяет хранилище указателя функции от фабрики указателя функции (фабрика должна быть «глобальной», хранилище указателя функции должно быть локальным по отношению кany), но это только вопрос дизайна.
 Nir Friedman08 авг. 2016 г., 21:12
Мне кажется, что когда я пытался делать подобные вещи в прошлом, я в конце концов осознал, что по сути все возвращается к виртуальным шаблонам, и тот факт, что они не допускаются языком. Я уверен, что что-то возможно, но, безусловно, многие из более хороших решений невозможны по этой причине.
 Nir Friedman09 авг. 2016 г., 01:53
@Yakk Если существуют виртуальные шаблоны, тоany может просто иметь шаблонную виртуальную функцию под названиемapply который принял вариадический функтор, и в производномany (который знает о типе), реализацияapply вызовет функтор для производного типа. В этом смысле виртуальные шаблоны легко решат вашу проблему. С вариантами, проблема легко решаема, потому что они используют switch-case, а не виртуальные, как косвенное указание времени выполнения; это ключевое отличие.

Ответы на вопрос(2)

std::aligned_storage и размещение новых. Кроме того, он поддерживает сохраняющие состояние и локальные функторы (что подразумевает невозможность записиsuper_any<&print>, посколькуprint может быть локальной переменной).

any_method:

template<class F, class Sig> struct any_method;

template<class F, class Ret, class... Args> struct any_method<F,Ret(Args...)> {
  F f;
  template<class T>
  static Ret invoker(any_method& self, boost::any& data, Args... args) {
    return self.f(boost::any_cast<T&>(data), std::forward<Args>(args)...);
  }
  using invoker_type = Ret (any_method&, boost::any&, Args...);
};

make_any_method:

template<class Sig, class F>
any_method<std::decay_t<F>,Sig> make_any_method(F&& f) {
  return { std::forward<F>(f) };
}

super_any:

template<class...OperationsToTypeErase>
struct super_any {
  boost::any data;
  std::tuple<typename OperationsToTypeErase::invoker_type*...> operations = {};

  template<class T, class ContainedType = std::decay_t<T>>
  super_any(T&& t)
    : data(std::forward<T>(t))
    , operations((OperationsToTypeErase::template invoker<ContainedType>)...)
  {}

  template<class T, class ContainedType = std::decay_t<T>>
  super_any& operator=(T&& t) {
    data = std::forward<T>(t);
    operations = { (OperationsToTypeErase::template invoker<ContainedType>)... };
    return *this;
  }
};

оператор -> *:

template<class...Ops, class F, class Sig,
  // SFINAE filter that an op matches:
  std::enable_if_t< std::disjunction< std::is_same<Ops, any_method<F,Sig>>... >{}, int> = 0
>
auto operator->*( super_any<Ops...>& a, any_method<F,Sig> f) {
  auto fptr = std::get<typename any_method<F,Sig>::invoker_type*>(a.operations);
  return [fptr,f, &a](auto&&... args) mutable {
    return fptr(f, a.data, std::forward<decltype(args)>(args)...);
  };
}

Использование:

#include <iostream>
auto print = make_any_method<void(std::ostream&)>(
  [](auto&& self, auto&& os){ os << self; }
);

using printable_any = super_any<decltype(print)>;

printable_any bob = 7; // sets up the printing data attached to the any

int main() {
  (bob->*print)(std::cout); // prints 7
  bob = 3.14159;
  (bob->*print)(std::cout); // prints 3.14159
}

Жить

 Yakk - Adam Nevraumont10 авг. 2016 г., 18:01
Slick, используя сигнатуру указателя функции, включая типany_method, но это принципиально ограничивает вас уникальным типомany_methodс (по крайней мере, на том же объекте). Я не вижу способа перейти наauto* аргументы указателя вsuper_any<&print> без труда. Я думаю, что взломать с участиемauto* тег. Я предполагаю, что это угловой случай, так как нам нужен полиморфизм времени компиляции вany_method, а такжеauto&& self в лямбде это самый простой способ сделать это. Гораздо меньше моего! (+1)

boost::any, так как у меня нет компилятора C ++ 17.

Синтаксис, который мы в итоге получаем:

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

что почти оптимально. С тем, что я считаю простыми изменениями в C ++ 17, это должно выглядеть так:

constexpr any_method<void(std::ostream&)> print =
  [](auto&& p, std::ostream& t){ t << p << "\n"; };

super_any<&print> a = 7;

(a->*print)(std::cout);

В C ++ 17 я бы улучшил это, взявauto*... указателей наany_method вместоdecltype шум.

Наследование публично отany немного рискованно, как будто кто-то беретany с вершины и изменяет его,tuple изany_method_data будет устаревшим. Вероятно, мы должны просто подражать всемуany интерфейс, а не наследовать публично.

@dyp написал подтверждение концепции в комментариях к ОП. Это основано на его работе, очищено с помощью семантики значения (украдено изboost::any) добавил. Решение @ cpplearner, основанное на указателях, было использовано для его сокращения (спасибо!), а затем я добавил оптимизацию vtable.

Сначала мы используем тег для передачи типов:

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, 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...)>
{
  using type = R(*)(boost::any&, any_method const*, Args...);
  template<class T>
  type operator()( tag_t<T> )const{
    return [](boost::any& self, any_method const* method, Args...args) {
      return (*method)( boost::any_cast<T&>(self), decltype(args)(args)... );
    };
  }
};

Теперь мы не хотим хранить указатель функции для каждой операции в нашемsuper_any, Таким образом, мы объединяем указатели функций в vtable:

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;

Это ищет методы, которые super any поддерживает для SFINAE:

template<class super_any, class method>
struct super_method_applies : std::false_type {};

template<class M0, class...Methods, class method>
struct super_method_applies<super_any_t<M0, Methods...>, method> :
    std::integral_constant<bool, std::is_same<M0, method>{}  || super_method_applies<super_any_t<Methods...>, method>{}>
{};

Это указатель псевдо-метода, какprint, что мы создаем глобально иconstLY.

Мы храним объект, с которым мы строим это внутриany_method, Обратите внимание, что если вы построите его с не лямбдатип этогоany_method используется как часть механизма отправки.

template<class Sig, class F>
struct any_method {
  using signature=Sig;

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< std::decay_t<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, class F>
any_method<Sig, 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...> {
private:
  template<class T>
  T* get() { return boost::any_cast<T*>(this); }

public:
  template<class T,
    std::enable_if_t< !std::is_same<std::decay_t<T>, super_any_t>{}, int>* =nullptr
  >
  super_any_t( T&& t ):
    boost::any( std::forward<T>(t) )
  {
    using dT=std::decay_t<T>;
    this->change_type( tag<dT> );
  }

  super_any_t()=default;
  super_any_t(super_any_t&&)=default;
  super_any_t(super_any_t const&)=default;
  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_const_t<std::remove_reference_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"; });

const auto wont_work = make_any_method<void(std::ostream&)>([](auto&& p, std::ostream& t){ t << p << "\n"; });

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

  // (a->*wont_work)(std::cout);

  double d = 4.2;
  a = d;

  (a->*print)(std::cout);
  (a->*wprint)(std::wcout);

  (a2->*print)(std::cout);
  (a2->*wprint)(std::wcout);

  // a = X{}; // generates an error if you try to store a non-printable
}

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

Сообщение об ошибке при попытке сохранить непечатныйstruct X{}; внутриsuper_any кажется разумным, по крайней мере, на лязг

main.cpp:150:87: error: invalid operands to binary expression ('std::ostream' (aka 'basic_ostream<char>') and 'X')
const auto x0 = make_any_method<void(std::ostream&)>([](auto&& p, std::ostream& t){ t << p << "\n"; });

это происходит в тот момент, когда вы пытаетесь назначитьX{} вsuper_any<decltype(x0)>.

Структураany_method достаточно совместим сpseudo_method это действует аналогично для вариантов, которые они, вероятно, могут быть объединены.

Я использовал ручной vtable здесь, чтобы сохранить затраты на стирание типа до 1 указателя наsuper_any, Это добавляет стоимость перенаправления к каждому вызову any_method. Мы могли бы хранить указатели прямо вsuper_any очень легко, и нетрудно сделать этот параметрsuper_any, В любом случае, в случае 1 стертого метода, мы должны просто сохранить его напрямую.

Два разныхany_methodодного типа (скажем, оба содержат указатель на функцию) порождают один и тот же типsuper_any, Это вызывает проблемы при поиске.

Различать их немного сложно. Если мы изменилиsuper_any принятьauto* any_methodмы могли бы связать все идентичного типаany_methods в кортеже vtable, затем выполните линейный поиск подходящего указателя, если их больше 1. Линейный поиск должен быть оптимизирован компилятором, если вы не делаете что-то безумное, как передача ссылки или указателя на конкретныйany_method мы используем.

Однако это выходит за рамки этого ответа; наличия этого улучшения пока достаточно.

Кроме того,->* можно взять указатель (или даже ссылку!) на левой стороне, позволяя ему обнаружить это и передать его лямбде. Это может сделать его действительно «любым методом», так как он работает с вариантами, super_anys и указателями с этим методом.

С небольшимif constexpr работать, лямбда может переходить на выполнение ADL или вызов метода в каждом случае.

Это должно дать нам:

(7->*print)(std::cout);

((super_any<&print>)(7)->*print)(std::cout); // C++17 version of above syntax

((std::variant<int, double>{7})->*print)(std::cout);

int* ptr = new int(7);
(ptr->*print)(std::cout);

(std::make_unique<int>(7)->*print)(std::cout);
(std::make_shared<int>(7)->*print)(std::cout);

сany_method просто "делать правильные вещи" (который питает ценностьstd::cout <<).

 Yakk - Adam Nevraumont11 июн. 2018 г., 18:04
@ tower120 Вы решили свою проблему, не используя super_any типа стирания типа, используя обычные виртуальные функции? Если нет, то я даже не знаю, как поговорить с вами о решении в этой более сложной области. Я не могу сказать из вашего ответа. Но решение для двойной диспетчеризации в случае виртуального наследования и решение здесь будут в основном одинаковыми. Ваше «предполагаемое» решение не похоже на случай виртуальной двойной отправки, поэтому я запутался.
 W.F.09 авг. 2016 г., 10:52
@TemplateRex Я думаю, что Yakk имел в виду, что ни один из компиляторов не поддерживает полностью C ++ 17 здесь параметры автоматического шаблона ...
 TemplateRex09 авг. 2016 г., 09:59
«поскольку у меня нет компилятора C ++ 17». Вы имеете в виду, кроме Wandbox и других онлайн-компиляторов? Также: apt.llvm.org.
 Yakk - Adam Nevraumont11 июн. 2018 г., 21:27
@ tower120 Так что это возможно, но это не то, о чем вы должны говорить в теме комментариев в несвязанном вопросе о переполнении стека. Я продолжал говорить "двойная проблема отправки". Пока вы не поймете эту проблему, у меня даже нетзапас слов поговорить с тобой об этом. И нет, я не собираюсь решать обе проблемы одновременно в комментариях к SO, но я буду утверждать, что это разрешимо в зависимости от точных деталей проблемы, о которой я даже не могу говорить, пока вы на самом деле не сделаете домашнюю работу.
 Yakk - Adam Nevraumont11 июн. 2018 г., 21:26
@ tower120 Хорошо. Так что успокойся и погугли "двойная отправка". Двойная диспетчеризация - это то, как вы фактически отправляете одну функцию на основе динамического типа двух ее аргументов (скажем, указатель this и аргумент функции). Существует целая куча работ по двойной диспетчеризации в C ++, включаябумага Бьярна, Существует целый ряд решений, от ручных посетителей до использования вариантов и автоматического написания методов с использованием CRTP вплоть до статьи Бьярна. Эта проблемаполностью ортогонально к super_any.
 Yakk - Adam Nevraumont09 авг. 2016 г., 13:22
@dyp ручной vtable, где мы создаем указатель на статическийtuple созданный на основе хранимого типа, будет лучше. Затраты на один указатель на супер-любой экземпляр вместо одного на метод на экземпляр. Это позволяет избежать использованияnew (либо размещение, либо нет). Размещение нового не годится, так как требует «удачи», чтобы получить правильный размер (или статические утверждения, или не безумные компиляторы), плюс добавляет уровень индикации, если не очень осторожен.
 tower12011 июн. 2018 г., 14:37
Можно ли избавиться от подписи вany_method_function ? Так можно ли было использовать параметры шаблона? Как std :: вариант с std :: посещения.
 tower12011 июн. 2018 г., 17:39
@ Yakk-AdamNevraumont Намерение - иметь этот интерфейсcoliru.stacked-crooked.com/a/a1cc393ffd2aa879 , В вашемsuper_any, во время emplace, вы храните "функтор" сtag<T>и по вызовуany_cast к этому конкретному типу. Но для того, чтобы использовать функтор, вам нужно иметь его сигнатуру, которая ограничивает вас использованием только конкретных типов. Противоположный способ сделать это - иметь any_visitor с набором KNOWN типов и попробовать any_cast для них один за другим, затем вызвать посетителя (обратные вызовы могут иметь аргументы шаблона). Я не понимаю, как это можно сделать с или без стирания типа.
 Yakk - Adam Nevraumont11 июн. 2018 г., 15:22
@ Башня, что нам двойная проблема отправки. Исследуйте решения без стирания tyoe; подобное можно сделать здесь. Но это ортогональные проблемы.
 tower12011 июн. 2018 г., 19:49
@ Yakk-AdamNevraumont Чтобы решить эту проблему с помощью виртуальных функций, вам нужны шаблоны виртуальных функций ... "предназначено" - это не решение - это то, о чем я спрашиваю; вот немного измененная версияpastebin.com/hXXPLNFP , Единственное отличие от вашего РАБОЧЕГО решения -make_any_method не имеет подписи функции. Сначала я спросил, это выполнимо? Вы сказали, что есть способ сделать это. Я этого не вижу
 dyp09 авг. 2016 г., 09:33
Я думаю, что должно быть возможно использовать виртуальные функции вместо указателей на функции, создав новый тип, который получен из экземпляров шаблона класса, которые создают необходимый код внутриsuper_any_t::set_operation_to, При множественном наследовании это должно быть примерно таким же коротким, как назначение указателям на функции. Так как вы ограничиваете входные функции, чтобы они оставались без состояния / чистыми, эти указатели сохраняются в vtable один раз засписок типов входных функций кажется возможным.

Ваш ответ на вопрос