Russian Qt Forum
Май 23, 2024, 23:01 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2 3 4   Вниз
  Печать  
Автор Тема: Паттерн visitor для boost::any  (Прочитано 25292 раз)
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2094



Просмотр профиля
« : Сентябрь 04, 2015, 13:58 »

Навеяно этой темой 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;
}
 

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

Спасибо за внимание  Улыбающийся
 
« Последнее редактирование: Октябрь 24, 2015, 22:22 от m_ax » Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
__Heaven__
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2130



Просмотр профиля
« Ответ #1 : Октябрь 21, 2015, 10:51 »

m_ax, а расскажите, пожалуйста, в каких задачах вы используете шаблон посетитель?
Записан
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2094



Просмотр профиля
« Ответ #2 : Октябрь 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) + "; "; // Объединяем строки
 

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

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
__Heaven__
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2130



Просмотр профиля
« Ответ #3 : Октябрь 21, 2015, 12:00 »

Я как раз и хотел не абстрактный пример, а реальный. Не совсем понятно, в каких задачах у нас может появиться гетерогенная коллекция. Увы, не встречался с таким.
Записан
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #4 : Октябрь 21, 2015, 12:07 »

По сути это набор объектов с общим предком, имеющим виртуальный метод типа doSomething().
Внешний контроллер просто вызывает этот метод для каждого объекта.
И каждый объект может doSomething() имплементировать по-своему.
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
__Heaven__
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2130



Просмотр профиля
« Ответ #5 : Октябрь 21, 2015, 12:10 »

Да устройство паттерна я понимаю Улыбающийся
Хочется услышать реальные задачи.
Записан
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2094



Просмотр профиля
« Ответ #6 : Октябрь 21, 2015, 12:15 »

По сути это набор объектов с общим предком, имеющим виртуальный метод типа doSomething().
Внешний контроллер просто вызывает этот метод для каждого объекта.
И каждый объект может doSomething() имплементировать по-своему.
Только здесь не нужен общий предок. И никаких виртуалов. А это делает этот патерн более гибким, чем классический полиморфизм.
Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
ssoft
Программист
*****
Offline Offline

Сообщений: 579


Просмотр профиля
« Ответ #7 : Октябрь 21, 2015, 12:24 »

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

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

Записан
__Heaven__
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2130



Просмотр профиля
« Ответ #8 : Октябрь 21, 2015, 12:50 »

Хороший пример. Спасибо.
Записан
ssoft
Программист
*****
Offline Offline

Сообщений: 579


Просмотр профиля
« Ответ #9 : Октябрь 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?
Записан
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #10 : Октябрь 21, 2015, 13:21 »

По сути это набор объектов с общим предком, имеющим виртуальный метод типа doSomething().
Внешний контроллер просто вызывает этот метод для каждого объекта.
И каждый объект может doSomething() имплементировать по-своему.
Только здесь не нужен общий предок. И никаких виртуалов. А это делает этот патерн более гибким, чем классический полиморфизм.

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

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #11 : Октябрь 21, 2015, 13:23 »

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

опередели меня Улыбающийся
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2094



Просмотр профиля
« Ответ #12 : Октябрь 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?
Это уже другой круг задач. А делать из визитора супер класс на все случаи жизни тож не айс)  

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

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
ssoft
Программист
*****
Offline Offline

Сообщений: 579


Просмотр профиля
« Ответ #13 : Октябрь 21, 2015, 14:43 »

Цитировать
Если заранее не известны возможные типы, то и в этом паттерне исчезает всякий смысл. Ведь когда мы проходимся по коллекции, мы уже должны знать как нам поступать с тем или иным объектом.

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

Сообщений: 2094



Просмотр профиля
« Ответ #14 : Октябрь 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;
 


Всё) Какие проблемы? Улыбающийся
Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
Страниц: [1] 2 3 4   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.339 секунд. Запросов: 22.