Russian Qt Forum

Qt => Кладовая готовых решений => Тема начата: m_ax от Сентябрь 04, 2015, 13:58



Название: Паттерн visitor для boost::any
Отправлено: m_ax от Сентябрь 04, 2015, 13:58
Навеяно этой темой http://www.prog.org.ru/topic_29244_15.html#lastPost (http://www.prog.org.ru/topic_29244_15.html#lastPost)

Паттерн visitor весьма удобная и полезная штука при работе с boost::variant. Однако, иногда бывает удобнее работать не с variant, а с any (boost::any).
Оказывается, можно легко перенести эту концепцию и на этот случай.

Небольшой пример, иллюстрирующий идею:
Код
C++ (Qt)
#include <iostream>
#include <list>
#include <string>
 
#include "any_visitor.h"
#include <boost/any.hpp>
 
 
struct to_double : public any_visitor<double, type_list<double, int, float,  std::string>> /* Первый аргумент - возвращаемый тип, второй - список всех возможных типов, для хранимого в any значения.
                                                                                         Если нужный тип не будет найден, из этого списка, будет выкинуто исключение std::bad_cast. */

{
   double operator()(const std::string & str) const { return std::stod(str); }
 
   template <class T>
   double operator()(const T & val) const { return val; }
};
 
 
struct to_string : public any_visitor<std::string, type_list<double, int, float, std::string>>
{
   std::string operator()(const std::string & str) const { return str; }
 
   template <class T>
   std::string operator()(const T & val) const { return std::to_string(val); }
};
 
 
 
int main()
{
 
  std::list<boost::any> many = {123, 3.14, 5.6f, std::string("2.71")};
 
  double sum = 0.0;
 
  for (const auto & x : many)
      sum += apply_any_visitor(to_double(), x); // считаем сумму элементов
 
  std::cout << sum << std::endl;
 
  std::string str;
  for (const auto & x : many)
      str += apply_any_visitor(to_string(), x) + "; "; // Объединяем строки
 
  std::cout << str << std::endl;
 
   return 0;
}
 

Исходники приаттачены.

Спасибо за внимание  :)
 


Название: Re: Паттерн visitor для boost::any
Отправлено: __Heaven__ от Октябрь 21, 2015, 10:51
m_ax, а расскажите, пожалуйста, в каких задачах вы используете шаблон посетитель?


Название: Re: Паттерн visitor для boost::any
Отправлено: m_ax от Октябрь 21, 2015, 11:27
m_ax, а расскажите, пожалуйста, в каких задачах вы используете шаблон посетитель?
В ситуациях, когда имеется некоторая гетерогенная коллекция и нужно выполнить над её элементами какое-либо действие. Но поскольку типы элементов могут быть различными, то и сами действия могут отличаться.. Тогда визитёр очень кстатии.

Например, как в примере выше, у нас есть коллекция
Код
C++ (Qt)
std::list<boost::any> many = {123, 3.14, 5.6f, std::string("2.71")};
 

которая содержит числа в разных представлениях (double, string, int, float).
С визитёром мы можем пройтись по ней и, например, посчитать сумму
Код
C++ (Qt)
double sum = 0.0;
 
  for (const auto & x : many)
      sum += apply_any_visitor(to_double(), x); // считаем сумму элементов
 
   
а можем их объединить в одну строку:
Код
C++ (Qt)
std::string str;
  for (const auto & x : many)
      str += apply_any_visitor(to_string(), x) + "; "; // Объединяем строки
 

Это лишь один абстрактный пример.. Короче, когда в коллекции могут быть различные объекты, каждый из которых  "знает что делать" и нам нужно пройтись по всей коллекции, тогда визитёр и применяется..


Название: Re: Паттерн visitor для boost::any
Отправлено: __Heaven__ от Октябрь 21, 2015, 12:00
Я как раз и хотел не абстрактный пример, а реальный. Не совсем понятно, в каких задачах у нас может появиться гетерогенная коллекция. Увы, не встречался с таким.


Название: Re: Паттерн visitor для boost::any
Отправлено: Racheengel от Октябрь 21, 2015, 12:07
По сути это набор объектов с общим предком, имеющим виртуальный метод типа doSomething().
Внешний контроллер просто вызывает этот метод для каждого объекта.
И каждый объект может doSomething() имплементировать по-своему.


Название: Re: Паттерн visitor для boost::any
Отправлено: __Heaven__ от Октябрь 21, 2015, 12:10
Да устройство паттерна я понимаю :)
Хочется услышать реальные задачи.


Название: Re: Паттерн visitor для boost::any
Отправлено: m_ax от Октябрь 21, 2015, 12:15
По сути это набор объектов с общим предком, имеющим виртуальный метод типа doSomething().
Внешний контроллер просто вызывает этот метод для каждого объекта.
И каждый объект может doSomething() имплементировать по-своему.
Только здесь не нужен общий предок. И никаких виртуалов. А это делает этот патерн более гибким, чем классический полиморфизм.


Название: Re: Паттерн visitor для boost::any
Отправлено: ssoft от Октябрь 21, 2015, 12:24
Я как раз и хотел не абстрактный пример, а реальный. Не совсем понятно, в каких задачах у нас может появиться гетерогенная коллекция. Увы, не встречался с таким.

Например, вы реализуете приложение типа Excel. Заранее вы не можете определить какой тип данных пользователь введет в ячейку таблицы и как правильно с ним работать. Вот отсюда и появляются всякие гетерогенные контейнеры и т.п.
Вообще гетерогенность не такая уж и редкость.



Название: Re: Паттерн visitor для boost::any
Отправлено: __Heaven__ от Октябрь 21, 2015, 12:50
Хороший пример. Спасибо.


Название: Re: Паттерн visitor для boost::any
Отправлено: ssoft от Октябрь 21, 2015, 13:13
В начальном примере все возможные типы известны заранее и описаны с помощью

Код
C++ (Qt)
type_list<double, int, float,  std::string>
 

Предположим что заранее такого перечня не существует, тогда реализация могла бы выглядеть примерно так

Код
C++ (Qt)
template < typename _Type >
struct to_double_helper;
 
template <>
struct to_double_helper< double >
{
   static double execute ( const boost::any & value )
   {
       return boost::any_cast< double >( value );
   }
};
 
template <>
struct to_double_helper< std::string >
{
   static double execute ( const boost::any & value )
   {
       return std::stod( boost::any_cast< std::string >( value ) );
   }
};
 
double to_double ( const boost::any & value )
{
   if ( value.type() == typeid( double ) )
       return to_double_helper< double >::execute( value );
   if ( value.type() == typeid( std::string ) )
       return to_double_helper< std::string >::execute( value );
   //...
 
 
   return 0;
}
 
int main()
{
   std::list< boost::any > many = {123, 3.14, 5.6f, std::string("2.71")};
   double sum = 0.0;
 
   for ( const auto & x : many )
       sum += to_double( x );
 
   std::cout << sum << std::endl;
   return 0;
}
 

Можно заменить содержимое to_double на что-то вроде

Код
C++ (Qt)
typedef double ( *to_double_function )( const boost::any & );
 
to_double_function to_double_function_for_type ( const std::type_info & type )
{
   to_double_function result = to_double_function();
   //...
   return result;
}
 
double to_double ( const boost::any & value )
{
   return (*to_double_function_for_type( value.type() ) )( value );
}
 

но это требует определение соотношений (std::type_info, to_double_function) в run-time, на этапе выполнения.

Есть какие нибудь идеи чтобы тоже самое реализовать в compile-time, но без формирования предварительного перечня типов, а имея только специализации to_double_helper?


Название: Re: Паттерн visitor для boost::any
Отправлено: Racheengel от Октябрь 21, 2015, 13:21
По сути это набор объектов с общим предком, имеющим виртуальный метод типа doSomething().
Внешний контроллер просто вызывает этот метод для каждого объекта.
И каждый объект может doSomething() имплементировать по-своему.
Только здесь не нужен общий предок. И никаких виртуалов. А это делает этот патерн более гибким, чем классический полиморфизм.

В чем гибкость? По сути, это просто эмуляция полиморфизма через темплейт :)
А если тип объектов будет известен только в рантайме - то не сработает :(
Ну или придется извращаться до посинения...


Название: Re: Паттерн visitor для boost::any
Отправлено: Racheengel от Октябрь 21, 2015, 13:23
но это требует определение соотношений (std::type_info, to_double_function) в run-time, на этапе выполнения.

опередели меня :)


Название: Re: Паттерн visitor для boost::any
Отправлено: m_ax от Октябрь 21, 2015, 13:44
Цитировать
Предположим что заранее такого перечня не существует,
Если заранее не известны возможные типы, то и в этом паттерне исчезает всякий смысл. Ведь когда мы проходимся по коллекции, мы уже должны знать как нам поступать с тем или иным объектом.

 
Цитировать
тогда реализация могла бы выглядеть примерно так
Код
C++ (Qt)
...
double to_double ( const boost::any & value )
{
   if ( value.type() == typeid( double ) )
       return to_double_helper< double >::execute( value );
   if ( value.type() == typeid( std::string ) )
       return to_double_helper< std::string >::execute( value );
   //...
 
 
   return 0;
}
 
Так паттерн визитор  как раз и избавляет нас от  таких дубовых if-else-switch конструкций.    :) Собственно, он для этого и создан)

Цитировать
Есть какие нибудь идеи чтобы тоже самое реализовать в compile-time, но без формирования предварительного перечня типов, а имея только специализации to_double_helper?
Это уже другой круг задач. А делать из визитора супер класс на все случаи жизни тож не айс)  

Цитировать
В чем гибкость? По сути, это просто эмуляция полиморфизма через темплейт
Нет не просто эмуляция) Здесь не нужно наследоваться, определять кучу виртуалов и т.д. Попробуйте с полиморфизмом сделать пример выше)


Название: Re: Паттерн visitor для boost::any
Отправлено: ssoft от Октябрь 21, 2015, 14:43
Цитировать
Если заранее не известны возможные типы, то и в этом паттерне исчезает всякий смысл. Ведь когда мы проходимся по коллекции, мы уже должны знать как нам поступать с тем или иным объектом.

В данном случае в самом паттерне смысл тот же, просто такая реализация уже не подходит. Представьте ситуацию, что есть сторонняя библиотека, в которой реализован алгоритм с использованием visitor, например, как в Вашем примере, но ее нельзя использовать для своего пользовательского типа MyData, даже если существует способ преобразовать MyData в double. Отсюда и желание иметь подобную реализацию без формирования априори всего перечня возможных типов. Хотя перечень типов на момент компиляции известен, но оооочень длинный.


Название: Re: Паттерн visitor для boost::any
Отправлено: m_ax от Октябрь 21, 2015, 20:08
Цитировать
Представьте ситуацию, что есть сторонняя библиотека, в которой реализован алгоритм с использованием visitor, например, как в Вашем примере, но ее нельзя использовать для своего пользовательского типа MyData, даже если существует способ преобразовать MyData в double.
Ну это легко решается..
Хорошо, пусть у нас есть сторонняя библиотека, использующая визитёр для any. Для примера рассмотрим только визитёр to_double.
Код
C++ (Qt)
typedef type_list<double, int, float,  std::string> default_type_list;
 
struct to_double : public any_visitor<double, default_type_list>
{
   double operator()(const std::string & str) const { return std::stod(str); }
 
   template <class T>
   double operator()(const T & val) const { return val; }
};
 

Это всё часть сторонней библиотеки.

Теперь нам в нашем коде понадобилось добавить возможность обрабатывать, скажем, комплексные числа. Тогда мы просто пишем:
Код
C++ (Qt)
struct to_double_advance : public any_visitor<double, merge<std::complex<double>, default_type_list>::type>
{
   double operator()(const std::complex<double> & c) const { return c.real(); }
 
   template <class T>
   double operator()(const T & val) const { return to_double()(val); }
};
 

И теперь просто используем его:

Код
C++ (Qt)
std::list<boost::any> many = {123, 3.14, 5.6f, std::string("2.71"), std::complex<double>(123.4, 5.6)};
 
      double sum = 0.0;
 
      for (const auto & x : many)
          sum += apply_any_visitor(to_double_advance(), x);
 
      std::cout << sum << std::endl;
 


Всё) Какие проблемы? :)


Название: Re: Паттерн visitor для boost::any
Отправлено: Racheengel от Октябрь 22, 2015, 00:48
На самом деле, проблем я вижу как минимум 3...

1 - опять же, подразумевается, что std::complex<double> - это тип, известный на момент компиляции. Но, скажем, у нас есть система классов, "скрытая" в библиотеке. Наружу открыт лишь интерфейс базового класса. Каким образом обработать типы, неизвестные заранее?

2 - в примере перегружается оператор (), что абсолютно неочевидно. Почему () должен обозначать приведение к типу, заданному в параметре шаблона? Что скажет выражение вроде to_double() ? Это вызов конструктора по умолчанию? А вот и нет :)

3 - это уже не C++. Это другой язык  :-\


Название: Re: Паттерн visitor для boost::any
Отправлено: Old от Октябрь 22, 2015, 09:13
Но, скажем, у нас есть система классов, "скрытая" в библиотеке. Наружу открыт лишь интерфейс базового класса. Каким образом обработать типы, неизвестные заранее?
Давайте забудем про шаблоны. Покажите как вы обрабатываете неизвестные типы на старом добром С++. :)

Что скажет выражение вроде to_double() ? Это вызов конструктора по умолчанию? А вот и нет :)
А вот и да. :)
Это создние объекта. А следующие скобки уже будут вызывать оператор ().

3 - это уже не C++. Это другой язык  :-\
Почему, это компилируется компилятором С++? :)


Название: Re: Паттерн visitor для boost::any
Отправлено: Igors от Октябрь 22, 2015, 09:18
1 - опять же, подразумевается, что std::complex<double> - это тип, известный на момент компиляции. Но, скажем, у нас есть система классов, "скрытая" в библиотеке. Наружу открыт лишь интерфейс базового класса. Каким образом обработать типы, неизвестные заранее?
Не думаю что это проблема, просто базовый класс позовет virtual, в крайнем случае тип-указатель

2 - в примере перегружается оператор (), что абсолютно неочевидно.
ДАААА, и это капитально затрудняет понимание.

А вот насчет "реальных примеров" действительно туговато. С трудом выдавили "ячейки excel", что не кажется мне убедительным. Да и то сразу свалились в банальное if/else. Тогда почему бы не взять QVariant (где большого ума не надо). Но, насколько я понимаю, это стиль буста - возможно его решения гениальны, но он не дает классов которые можно просто "жрать" (как Qt).
Цитировать
В Qt хорошо, взял классик - и все работает
:)


Название: Re: Паттерн visitor для boost::any
Отправлено: ssoft от Октябрь 22, 2015, 09:24
Всё) Какие проблемы? :)

В реализации совсем другого посетителя to_double_advance проблем никаких нет  ;)

Вопрос стоял по-другому. Есть шаблонный внешний код, который использует внутри посетителя to_double, он нас всем устраивает, но необходимо чтобы он работал и для пользовательского типа MyData.
С MyData можно выполнять любые определения и специализации, а вот внешний код или посетителя менять нельзя. Предположим, что посетителя нельзя подменить и через параметры шаблона.
Вопрос, есть ли способ определить такого посетителя to_double?


Название: Re: Паттерн visitor для boost::any
Отправлено: Old от Октябрь 22, 2015, 09:40
Но, насколько я понимаю, это стиль буста - возможно его решения гениальны, но он не дает классов которые можно просто "жрать" (как Qt).
А что мы сейчас делаем с boost::any?  ::)
Берем готовый и "жрем" как в Qt. А конкретные обработчики данных описываем сами, как и в Qt.


Название: Re: Паттерн visitor для boost::any
Отправлено: ssoft от Октябрь 22, 2015, 09:45
А вот насчет "реальных примеров" действительно туговато. С трудом выдавили "ячейки excel", что не кажется мне убедительным. Да и то сразу свалились в банальное if/else. Тогда почему бы не взять QVariant (где большого ума не надо). Но, насколько я понимаю, это стиль буста - возможно его решения гениальны, но он не дает классов которые можно просто "жрать" (как Qt).
Цитировать
В Qt хорошо, взял классик - и все работает
:)

К сожалению QVariant абсолютно также плохо работает с пользовательскими типами - ни приведения, ни конвертации. Работает нормально только для базовых типов и типов Qt.


Название: Re: Паттерн visitor для boost::any
Отправлено: Igors от Октябрь 22, 2015, 09:48
Хорошо, вот есть у меня место с ужасными свитчами (даже с моей толерантностью). Когда-то я уже о нем рассказывал. Предельно упрощенно
Код
C++ (Qt)
struct CData {
int mType;
union {
  int mInt;
  float mFloat;
  bool mBool;
  ARGB mColor;
};
};
Заметим что типов больше чем членов union (int и float могут быть signed/unsigned). Есть контейнер таких CData и есть десяток (или больше) операций с этим контейнером. Увы, каждая - немаленький switch. Вроде бы напрашивается "any", но есть маааленькая проблемка - небольшая контекстность. Ну совсем чуть-чуть  :)

Когда выполняется запись в поток (ну и соответственно чтение) булевские CData пакуются. То есть неск идущих подряд булевских CData записываются одним интом с взведенными битами. Конечно это глупейшая экономия неск байт, но это уже проникло в плагины и менять себе дороже.

Как будет выглядеть проход any/many с учетом этого?

Спасибо


Название: Re: Паттерн visitor для boost::any
Отправлено: Old от Октябрь 22, 2015, 10:16
Когда-то я уже о нем рассказывал.
И тогда же вам посоветовали переписать его на С++, переделав структуру в дерево классов, каждый из которых выполняет заданную операцию. Что позволило бы полностью уйти от switch и упростить процесс добавления операций, исключив типичные ошибки с добавлением обработки в один switch и забыванием добавить в другой. Но вы же не пишите на С++...
А boost::any здесь никаких принципиальных решений для вас не добавит.


Название: Re: Паттерн visitor для boost::any
Отправлено: Igors от Октябрь 22, 2015, 10:53
Пожалуй я неправ говоря что это единственная проблема. Вот еще (ну может и не проблема, но как сделать не знаю)

- допустим мы гордо отказались от явно сохраненного типа (поле mType) и козырно собрали все в tuple, типа "ничего лишнего", только сами int, float и др. Но что же делать если идентификатор типа ('SINT', 'UINT' и др) нам понадобился? А такая необходимость есть, напр для внешнего редактора ресурсов, и для записи/чтения.


Название: Re: Паттерн visitor для boost::any
Отправлено: m_ax от Октябрь 22, 2015, 11:32
Цитировать
С MyData можно выполнять любые определения и специализации, а вот внешний код или посетителя менять нельзя.
Вот это утверждение входит в разрез с идиалогией визитёрства, на мой взгляд) Идея ведь в том, чтоб дать возможность при изменении задачи, просто подменять соответствующих визитёров)
И более того, когда у нас появляется новый тип (complex, как в примере) мы же должны каким-то образом сообщить визитёру как с ним работать? А если его (визитёра) нельзя менять? И если API какой либо библиотеки, использующей этот механизм, такую возможность перекрывает, то стоит задуматься(

Цитировать
Предположим, что посетителя нельзя подменить и через параметры шаблона. Вопрос, есть ли способ определить такого посетителя to_double?

Здесь, наверное,  нет.. Хотя если написать свой moc для этого, то..  :)



Название: Re: Паттерн visitor для boost::any
Отправлено: m_ax от Октябрь 22, 2015, 12:03
Цитировать
- допустим мы гордо отказались от явно сохраненного типа (поле mType) и козырно собрали все в tuple, типа "ничего лишнего", только сами int, float и др. Но что же делать если идентификатор типа ('SINT', 'UINT' и др) нам понадобился?
Ну и в чём проблема то? 


Название: Re: Паттерн visitor для boost::any
Отправлено: ssoft от Октябрь 22, 2015, 12:09
Цитировать
Вот это утверждение входит в разрез с идиалогией визитёрства, на мой взгляд) ...

Здесь я согласен, похоже рассматриваемые задачи просто не предусматривают возможности решения с помощью идеологии визитеров).

Цитировать
Пожалуй я неправ говоря что это единственная проблема. Вот еще (ну может и не проблема, но как сделать не знаю)

- допустим мы гордо отказались от явно сохраненного типа (поле mType) и козырно собрали все в tuple, типа "ничего лишнего", только сами int, float и др. Но что же делать если идентификатор типа ('SINT', 'UINT' и др) нам понадобился? А такая необходимость есть, напр для внешнего редактора ресурсов, и для записи/чтения.

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

1. Изначально имеется класс, например, boost::any.
2. В добавок к нему реализованы классы фабрик, которые умеют генерировать boost::any.
Код
C++ (Qt)
 
struct AbstractAnyFabric
{
   virtual ~AbstractAnyFabric () {}
   virtual boost::any make () = 0;
};
 
template < typename _Type >
struct AnyFabric : public AbstractAnyFabric
{
   virtual boost::any make ()
   {
       return boost::any( _Type() );
   }
};
 

3. Для каждого типа реализован вызов уникального экземпляра фабрики (singleton)

Код:
template < typename _Type >
const AbstractAnyFactory & any_factory ()
{
    static const AnyFactory< _Type > factory;
    return factory;
}

4. Реализован механизм регистрации фабрик к функциям, например, к

Код
C++ (Qt)
Result foo ( const boost::any & )
 

5. Реализован механизм регистрации отображения любого идентификатора в фабрику.

Механизмы регистрации работают в run-time и предусматривают вызов методов аля qRegisterMetaType.
Вот этот run-time мне как раз не нравится, а решение в compile-time не нашел.

Такая система позволяет легко зарегистрировать и в дальнейшем использовать любую функциональность с boost::any.

Рассмотрим, например, сериализацию. Для этой задачи необходимо зарегистрировать отображения:

 - typeid( _Type ) в AnyFactory< _Type >,
 - AnyFactory< _Type > в уникальный ключ типа,
 - уникальный ключ типа в AnyFactory< _Type >,
 - AnyFactory< _Type > в метод сериализации,
 - AnyFactory< _Type > в метод десериализации.

В момент сериализации:

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

В момент десериализации:

 - зачитываем ключ,
 - для ключа находим фабрику,
 - через фабрику генерируем any
 - для фабрики находим метод десериализации,
 - выполняем десериализацию содержимого any.


Название: Re: Паттерн visitor для boost::any
Отправлено: Igors от Октябрь 22, 2015, 13:16
Если все возможные типы известны заранее, то можно решить задачу и с помощью визитеров.
У меня известны заранее

Если необходимо решить задачу в общем виде, то могу прокомментировать свое текущее решение. Так уж получилось, что по роду своей деятельности работать с неизвестными наперед типами приходится постоянно.
Не все, но примерно понял. Спасибо

Ну и в чём проблема то? 
Хорошо, делаем по образцу Вашего первого поста
Код
C++ (Qt)
struct to_TypeID : public any_visitor<uint, type_list<int, uint, float,  bool, ARGB>>
{
   uint operator()(const int &) const { return 'SINT'; }
   uint operator()(const uint &) const { return 'UINT'; }
   uint operator()(const float &) const { return 'REAL'; }
   uint operator()(const bool &) const { return 'BOOL'; }
   uint operator()(const ARGB &) const { return 'ARGB'; }
};
Такую городушку надо иметь на каждое действие (в данном случае это получить ID типа как число). Ну лады, надо так надо, пусть пока "выйгрыша" нет, потерпим. Но вот что делать с 2 моими типами которые в эту схему не вписались

- флот может быть любой ('REAL') или только положительный ('UREL')
- цвет может быть с альфой ('ARGB') или без ('CRGB')

Создавать для них классы? Конечно это несложно, но как-то неоднообразно выходит. Может тогда есть смысл оставить идентификатор типа как член класса?


Название: Re: Паттерн visitor для boost::any
Отправлено: m_ax от Октябрь 22, 2015, 13:35
Цитировать
Создавать для них классы? Конечно это несложно, но как-то неоднообразно выходит.
Наоборот, это даст однообразность. Если необходимо обеспечивать различное поведение (обработку) для типов REAL и UREAL, ARGB и CRGB во многих ситуациях, то лучше сразу вначале выделить все разнородные классы. И работать уже однообразно (в категориях визитёра) с этим множеством.  

И, кстатии, если все типы уже известны заранее и не предполагается добавление новых, то  более уместен boost::variant.. 


Название: Re: Паттерн visitor для boost::any
Отправлено: Igors от Октябрь 23, 2015, 09:28
Хорошо, не хочу показаться навязчивым, но все же как с "маленькими исключениями"?
Когда выполняется запись в поток (ну и соответственно чтение) булевские CData пакуются. То есть неск идущих подряд булевских CData записываются одним интом с взведенными битами. Конечно это глупейшая экономия неск байт, но это уже проникло в плагины и менять себе дороже.

Как будет выглядеть проход any/many с учетом этого?
А то часто бывает что конструкция-то красивая, а маленький шажок влево-вправо обходится безумно дорого. Здесь в поток пишутся только сами значения, предполагается что читающему известны типы и порядок.

Спасибо


Название: Re: Паттерн visitor для boost::any
Отправлено: ssoft от Октябрь 23, 2015, 10:19
Так тут можно так поступить. Правда не совсем визитер, но суть та же.

Код
C++ (Qt)
typedef type_list< double, int, std::string, float > list_of_types;
 
struct cals_sum : public any_visitor< double, list_of_types >
{
   double m_sum;
 
   cals_sum () : m_sum() {}
 
   double operator()( const std::string & str ) { return m_sum += std::stod(str); }
   template <class T>
   double operator()( const T & val ) { return m_sum += double( val ); }
};
 
 
struct to_string : public any_visitor< std::string, list_of_types >
{
   std::string operator()( const std::string & str ) const { return str; }
 
   template <class T>
   std::string operator()(const T & val) const { return std::to_string(val); }
};
 
int main()
{
   std::list< boost::any > many = {123, 3.14, 5.6f, std::string("2.71")};
 
   cals_sum visitor;
   for ( const auto & x : many )
       apply_any_visitor( visitor, x );
 
   std::cout << visitor.m_sum << std::endl;
   return 0;
}
 

Ну плюс модифицировать apply часть для возможности работы с неконстантными визитерами.

Код
C++ (Qt)
template <class Visitor, class TypeList>
struct any_cast_helper
{
   static typename Visitor::result_type get(const boost::any & any, Visitor & visitor)
   {
       if (any.type() == typeid(typename TypeList::first_type))
           return visitor(boost::any_cast<typename TypeList::first_type >(any));
 
       return any_cast_helper<Visitor, typename TypeList::next_type_list>::get(any, visitor);
   }
   static typename Visitor::result_type get(const boost::any & any, const Visitor & visitor)
   {
       if (any.type() == typeid(typename TypeList::first_type))
           return visitor(boost::any_cast<typename TypeList::first_type>(any));
 
       return any_cast_helper<Visitor, typename TypeList::next_type_list>::get(any, visitor);
   }
};
 
 
template <class Visitor>
struct any_cast_helper<Visitor, null_type>
{
   static typename Visitor::result_type get(const boost::any &, Visitor &) throw(std::bad_cast)
   {
       throw std::bad_cast();
   }
   static typename Visitor::result_type get(const boost::any &, const Visitor &) throw(std::bad_cast)
   {
       throw std::bad_cast();
   }
};
 
 
template <class Visitor>
typename Visitor::result_type apply_any_visitor(const Visitor & visitor, const boost::any & any)
{
   return any_cast_helper<Visitor, typename Visitor::type_list>::get(any, visitor);
}
 
template <class Visitor>
typename Visitor::result_type apply_any_visitor( Visitor & visitor, const boost::any & any)
{
   return any_cast_helper<Visitor, typename Visitor::type_list>::get(any, visitor);
}
 

Вместо расчета суммы можно осуществить паковку данных с учетом последовательности прихода.


Название: Re: Паттерн visitor для boost::any
Отправлено: m_ax от Октябрь 23, 2015, 12:18
Цитировать
Здесь в поток пишутся только сами значения, предполагается что читающему известны типы и порядок.

Пишите визитёра для записи в поток:
Код
C++ (Qt)
template <class Stream>
struct serialize : public any_visitor<void, type_list<int, uint, float,  bool, ARGB>>
{
   serialize(Stream & out) : m_out(out) {}
 
   template <class T>
   void operator()(const T & val) { m_out << val; }
 
private:
   Stream & m_out;
};
 

Цитировать
но все же как с "маленькими исключениями"?
А этим по логике должен заниматься Stream. Научите его паковать подряд идущие bool в int..


Название: Re: Паттерн visitor для boost::any
Отправлено: Old от Октябрь 23, 2015, 12:22
А этим по логике должен заниматься Stream. Научите его паковать подряд идущие bool в int..
Или используйте внешний серилизатор, который умеет это делать.


Название: Re: Паттерн visitor для boost::any
Отправлено: m_ax от Октябрь 23, 2015, 12:27
Цитировать
Ну плюс модифицировать apply часть для возможности работы с неконстантными визитерами.

Да, это нужная фича, спасибо)  

Или может тогда уж просто по значению его передавать
Код
C++ (Qt)
static typename Visitor::result_type get(const boost::any & any, Visitor visitor)
 
Визитёры, они ведь не слишком то уж жирные  :)


Название: Re: Паттерн visitor для boost::any
Отправлено: Igors от Октябрь 23, 2015, 12:57
А этим по логике должен заниматься Stream. Научите его паковать подряд идущие bool в int..
А откуда он может знать что это bool? Оно пишется как и int. Наследоваться от stream - ну явно "не то". В общем, я так и знал: вот парИть по концепциям - то да, а со всякими мелочами пусть дедушка сидит. Это же так "непринципиально"  :)


Название: Re: Паттерн visitor для boost::any
Отправлено: Old от Октябрь 23, 2015, 13:17
В общем, я так и знал: вот парИть по концепциям - то да, а со всякими мелочами пусть дедушка сидит.
Ну кто-то и это должен делать, если с концепциями не получается. :)


Название: Re: Паттерн visitor для boost::any
Отправлено: m_ax от Октябрь 23, 2015, 13:30
Цитировать
А откуда он может знать что это bool? Оно пишется как и int. Наследоваться от stream - ну явно "не то".
Не нужно наследоваться.. Как сказал Old достаточно внешнего сериализатора, который это умеет. Самый простейший вариант в пару строк:
Код
C++ (Qt)
class serializator
{
public:
   serializator(std::ofstream & out) : m_out(out) {}
 
   template <class T>
   friend serializator & operator<<(serializator &, const T &);
 
   friend serializator & operator<<(serializator &, bool);
 
private:
   int bool_packet;
   std::ofstream & m_out;
};
 
А в операторах << реализуете упаковку булов в bool_paket и т.д. и т.п..


Название: Re: Паттерн visitor для boost::any
Отправлено: Igors от Октябрь 23, 2015, 14:30
Самый простейший вариант в пару строк:
Та где ж "пару" если только хедер уже десяток, да и то неполный - напр забыли вытолкнуть последний набор флагов. Создали класс для такой мелочи, завязались на ofstream - нуу...  :'(

Опять-таки, где "выйгрыш"? Писали этажерку switch для каждого действия, теперь для каждого действия класс и в нем опять расписываем. Да, вот если действие повторяется - тогда задействуем уже готовый apply, а ветку switch придется заново рисовать. Ну навскидку я таких повторов не помню.

Ладно, все равно спасибо, хорошая получилась тема


Название: Re: Паттерн visitor для boost::any
Отправлено: Old от Октябрь 23, 2015, 14:32
Писали этажерку switch для каждого действия, теперь для каждого действия класс и в нем опять расписываем. Да, вот если действие повторяется - тогда задействуем уже готовый apply, а ветку switch придется заново рисовать. Ну навскидку я таких повторов не помню.
Вообще не одного попадания.

Ладно, все равно спасибо, хорошая получилась тема
Уже по моему четвертая про длинные switch. :)


Название: Re: Паттерн visitor для boost::any
Отправлено: m_ax от Октябрь 23, 2015, 18:45
Цитировать
Уже по моему четвертая про длинные switch.  :)
И что-то мне подсказывает, что ещё далеко не последняя  :)


Название: Re: Паттерн visitor для boost::any
Отправлено: Igors от Октябрь 24, 2015, 05:51
Кстати вот такое наблюдение. Часто приходилось читать что, мол, класс - это "сущность", понятие фундаментальное и все такое. Но с бустовским подходом это совсем не так. Здесь класс - нечто чисто техническое, типа оператора, никакой "сущностью" он не является. Если видим только сам класс (без его использования) - вряд ли возможно понять зачем он. В общем "узурпировали"  :)


Название: Re: Паттерн visitor для boost::any
Отправлено: Old от Октябрь 24, 2015, 08:28
Кстати вот такое наблюдение. Часто приходилось читать что, мол, класс - это "сущность", понятие фундаментальное и все такое. Но с бустовским подходом это совсем не так. Здесь класс - нечто чисто техническое, типа оператора, никакой "сущностью" он не является. Если видим только сам класс (без его использования) - вряд ли возможно понять зачем он. В общем "узурпировали"  :)
Это не так. Функтор это такая же сущность, выполняющая строго свою задачу. И функторы применяются не только в бусте, но и в огромном количестве других библиотек и программ, не обязательно плюсовых.
Хотите посмотреть на функторы в Qt, взгляните на файл qsortfilterproxymodel.cpp, они там в начале, и скажите, если смотреть только на них, вам понятно что они делают?


Название: Re: Паттерн visitor для boost::any
Отправлено: m_ax от Октябрь 24, 2015, 12:48
Добавил визитёру возможность изменять содержимое boost::any. Думаю, это полезная фича)
Т.е. сейчас можно делать так:
Код
C++ (Qt)
struct editor : public any_visitor<void, type_list<int, double, std::string>>
{
   void operator()(std::string & s) { s += " Qt5"; }
 
   void operator()(int & i) { i += 100500; }
 
   void operator()(double & d) { d = 3.14159; }
};
 
struct printer : public any_visitor<void, type_list<int, double, std::string>>
{
   template <class T>
   void operator()(const T & val) const { std::cout << val << std::endl; }
};
 
int main()
{
   std::list<boost::any> many = {13, 1.34834657, std::string("hello")};
 
   std::cout << "before:" << std::endl;
   for (auto & x : many)
       apply_any_visitor(printer(), x);
 
   for (auto & x : many)
       apply_any_visitor(editor(), x); // Editor изменяет значения элементов.
 
   std::cout << "after:" << std::endl;
   for (auto & x : many)
       apply_any_visitor(printer(), x);
 
   return 0;
}
 


Название: Re: Паттерн visitor для boost::any
Отправлено: Racheengel от Октябрь 24, 2015, 15:09
Вот не знаю как у кого, а у меня складывается впечатление, что подобные визитеры люди начинают использовать тогда, когда в существующую архитектуру встроить необходимые изменения уже не получается физически, не ломая структуру классов. Поэтому сверху накатывается некое "универсальное" решение, которое потом руководство проекта представляет как фичу, скрывая кривизну архитектуры и нежелание ее менять ;)

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


Название: Re: Паттерн visitor для boost::any
Отправлено: m_ax от Октябрь 24, 2015, 16:58
Цитировать
но имхо это из разряда костыльных паттернов, не?
Нет конечно) Визитёр это как раз пример очень гибкого расширяемого решения, в отличии бесконечных свитчей намертво привязанных к коду)   


Название: Re: Паттерн visitor для boost::any
Отправлено: m_ax от Октябрь 24, 2015, 22:27
Запилил ещё одну меташаблонную фичу: merge, которая объединяет текущий type_list со списком других типов, т.е.

Код
C++ (Qt)
typedef type_list<int, bool, float> default_type_list;
struct some_visitor : public any_visitor<void, merge<default_type_list, double, std::string, long /* и т.д. */ >::type>
{
...
};
 


Название: Re: Паттерн visitor для boost::any
Отправлено: Igors от Октябрь 25, 2015, 08:10
Запилил ещё одну меташаблонную фичу: merge, которая объединяет текущий type_list со списком других типов, т.е.
У меня это плохо укладывается в голове :) Покажите реализацию


Название: Re: Паттерн visitor для boost::any
Отправлено: m_ax от Октябрь 25, 2015, 11:14
Цитировать
У меня это плохо укладывается в голове  :) Покажите реализацию
Вот же:
Код
C++ (Qt)
namespace detail
{
   template<class, class>
   struct merge_helper;
 
   template <class T, class...Types>
   struct merge_helper<type_list<Types...>, T>
   {
       typedef type_list<T, Types...> type;
   };
 
} /* namespace detail */
 
template <class T, class A, class...Types>
struct merge
{
   typedef typename merge<typename detail::merge_helper<T, A>::type, Types...>::type type;
};
 
template <class T, class A>
struct merge<T, A>
{
   typedef typename detail::merge_helper<T, A>::type type;
};
 

Код
C++ (Qt)
typedef type_list<int, long, double> my_type_list;
 
std::cout << std::boolalpha << std::is_same<typename merge<my_type_list, bool, std::string>::type, type_list<std::string, bool, int, long, double>>::value << std::endl;