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

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

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

Сообщений: 2094



Просмотр профиля
« Ответ #45 : Февраль 17, 2015, 16:29 »

Цитировать
Вот это как раз можно легко реализовать.. и boost::variant для этого всё предоставляет. Как?
Это можно реализовать так:

Код
C++ (Qt)
#include <iostream>
#include <boost/variant.hpp>
 
 
template <class T>
struct numeric_visitor : public boost::static_visitor<T>
{
   T operator()(int val) const { return val; }
 
   T operator()(float val) const { return val; }
 
   T operator()(double val) const { return val; }
 
   template <class S>
   T operator()(S) const { return T(); } // или кидаем исключение..
};
 
template <class T>
struct string_visitor : public boost::static_visitor<T>
{
   T operator()(const std::string& val) const { return val; }
 
   template <class S>
   T operator()(S) const { return T(); } // или кидаем исключение
};
 
 
struct numeric_category : public boost::static_visitor<bool>
{
   bool operator()(double) const { return true; }
   bool operator()(float) const { return true; }
   bool operator()(int) const { return true; }
 
   template <class T>
   bool operator()(T) const { return false; }
 
};
 
struct string_category : public boost::static_visitor<bool>
{
   bool operator()(std::string) const { return true; }
 
   template <class T>
   bool operator()(T) const { return false; }
};
 
 
template <class V>
bool is_numeric(V v)
{
   return boost::apply_visitor(numeric_category(), v);
}
 
template <class V>
bool is_string(V v)
{
   return boost::apply_visitor(string_category(), v);
}
 
 
int main()
{
   typedef boost::variant<double, float, int, std::string> variant_t;
 
   variant_t v1 = 1.1234; // 10; 1.234f ...
   variant_t v2 = std::string("abc");
 
   if (is_numeric(v1))
       std::cout << "numeric: val = " << boost::apply_visitor(numeric_visitor<double>(), v1) << std::endl;
 
   if (is_string(v2))
       std::cout << "string: val = " << boost::apply_visitor(string_visitor<std::string>(), v2) << std::endl;
 
   return 0;
}
 

Цитировать
Как снимать с этого бабло?  Улыбающийся
Ну для человека у которого есть талант снимать бабло с if-else-switch простыней, то это даже не вопрос  Улыбающийся 
Записан

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

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

Сообщений: 2094



Просмотр профиля
« Ответ #46 : Февраль 17, 2015, 21:22 »

Хм.. можно сделать ещё лаконичнее и проще, используя boost::optional
Пример с самодельным вариантом:

Код
C++ (Qt)
#include <iostream>
#include <variant.h>
#include <string>
#include <boost/optional.hpp>
 
typedef variant<float, double, int, std::string> variant_t;
 
int main()
{
   variant_visitor<boost::optional<double> (variant_t)> is_numeric;
   is_numeric.reg<double>([](variant_t v) { return v.get<double>(); });
   is_numeric.reg<int>([](variant_t v) { return v.get<int>(); });
   is_numeric.reg<float>([](variant_t v) { return v.get<float>(); });
 
   variant_visitor<boost::optional<std::string> (variant_t)> is_string;
   is_string.reg<std::string>([](variant_t v) { return v.get<std::string>(); });
 
   variant_t v = 123.676;
   variant_t v2 = std::string("abc");
 
   if (is_numeric(v))
       std::cout << *is_numeric(v) << std::endl;
 
   if (is_string(v2))
       std::cout << *is_string(v2) << std::endl;
 
   return 0;
}
 

С boost::variant всё будет аналогично:
Код
C++ (Qt)
#include <iostream>
#include <string>
#include <boost/optional.hpp>
#include <boost/variant.hpp>
 
typedef boost::variant<float, double, int, std::string> variant_t;
 
struct is_numeric : public boost::static_visitor<boost::optional<double>>
{
   result_type operator()(float v) const { return v; }
   result_type operator()(double v) const { return v; }
   result_type operator()(int v) const { return v; }
 
   template <class R>
   result_type operator()(R) const { return result_type(); }
};
 
 
struct is_string : public boost::static_visitor<boost::optional<std::string>>
{
   result_type operator()(const std::string & v) const { return v; }
 
   template <class R>
   result_type operator()(R) const { return result_type(); }
};
 
int main()
{
   variant_t v = 123;
 
   if (boost::apply_visitor(is_numeric(), v))
       std::cout << *boost::apply_visitor(is_numeric(), v) << std::endl;
 
   if (boost::apply_visitor(is_string(), v))
       std::cout << *boost::apply_visitor(is_string(), v) << std::endl;
 
   return 0;
}
 
« Последнее редактирование: Февраль 17, 2015, 22:12 от m_ax » Записан

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

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

Сообщений: 11445


Просмотр профиля
« Ответ #47 : Февраль 18, 2015, 10:23 »

Как можно сортировать совместно числа и строки? Что больше 123 или "abc"?
Сообщите эти правила и я легко изменю функцию преобразования строки под ваши правила. Одну функцию исключительно для строки.
123 (число) должно быть меньше "abc". Здесь мы не ставим задачу сравнивать строки (посимвольно), пусть они сортируются "как получится", с этим никто не спорит. Но числа-то мы сравнивать хотим. Так сделайте чтобы сначала шли числа (что умеем/хотим сортировать), а потом уж все остальное (что сортировке не подлежит).

А это не возможно исправить без допущений, а с допущением делается легко и не принужденно и не убивает все ресурсы процессора на перебор типов.
Что-то не наблюдаю легкости. Напр задача чуть-чуть изменилась, теперь контейнер может содержать любые типы, но нас по прежнему интересуют только int, double и (валидный) string. И что-то я не соображу (по крайней мере сходу) как же это сделать с вумным обсервером  Непонимающий
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4349



Просмотр профиля
« Ответ #48 : Февраль 18, 2015, 10:28 »

Значит, все строки, которые не представляют числа, должны конвертироваться в максимальное число, например, std::numeric_limits<double>::max()
Тогда, после сортировки, они все будут болтаться в хвосте коллекции.
Записан
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2094



Просмотр профиля
« Ответ #49 : Февраль 18, 2015, 11:04 »

Значит, все строки, которые не представляют числа, должны конвертироваться в максимальное число, например, std::numeric_limits<double>::max()
Тогда, после сортировки, они все будут болтаться в хвосте коллекции.

Но чтоб товарищу igors'у не было обидно, что он так и не увидел лёгкости, покажу как это можно сделать по-другому ("его методом" в контексте визитёра)..  

Код
C++ (Qt)
variant_visitor<boost::optional<double> (variant_t)> is_numeric;
   is_numeric.reg<double>([](variant_t v) { return v.get<double>(); });
   is_numeric.reg<int>([](variant_t v) { return v.get<int>(); });
   is_numeric.reg<float>([](variant_t v) { return v.get<float>(); });
 
   is_numeric.reg<std::string>([](variant_t v)->boost::optional<double>
   {
       try {
           return std::stod(v.get<std::string>());
       } catch (...) {
           return boost::optional<double>();
       }
   } );
 
 
   std::list<variant_t> list;
   list.push_back(1);
   list.push_back(123.456);
   list.push_back(std::string("abc"));
   list.push_back(std::string("6483.238578"));
 
   list.sort([&](variant_t v1, variant_t v2)->bool
   {
       auto x = is_numeric(v1);
       auto y = is_numeric(v2);
       return (x && y) ? (*x < *y) : (x && !y);
   });
 
 

... Может небольшую статью об этом патерне написать?)
« Последнее редактирование: Февраль 18, 2015, 11:30 от m_ax » Записан

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

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

Сообщений: 11445


Просмотр профиля
« Ответ #50 : Февраль 18, 2015, 13:58 »

Вот пример попроще - но с той же проблемой
Код
C++ (Qt)
#include <iostream>
#include <string>
#include <vector>
 
struct MyStruct {
MyStruct( int val = 0 ) : mUseText(false), mVal(val) {}
MyStruct( const char * str ) : mUseText(true), mStr(str) {}
 
bool operator < ( const MyStruct & sec ) const
{
if (mUseText || sec.mUseText) return false; // wrong
return mVal < sec.mVal;
}
 
// data
bool mUseText;
union {
int mVal;
const char * mStr;
};
};
 
int main( void )
{
std::vector <MyStruct> vec;
vec.push_back(MyStruct("str1"));
vec.push_back(MyStruct(5));
vec.push_back(MyStruct(2));
vec.push_back(MyStruct("str2"));
vec.push_back(MyStruct(1));
vec.push_back(MyStruct("str3"));
 
std::sort(vec.begin(), vec.end());
 
for (size_t i = 0; i < vec.size(); ++i)
if (vec[i].mUseText)
std::cout << vec[i].mStr << std::endl;
else
std::cout << vec[i].mVal << std::endl;
 
return 0;
}
 
Вывод
Цитировать
str1
2
5
str2
1
str3
Как видим, дело не только в том что "валидные/невалидные" эл-ты перемешаны, но и валидные отсортированы неверно.
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4349



Просмотр профиля
« Ответ #51 : Февраль 18, 2015, 14:07 »

Как видим, дело не только в том что "валидные/невалидные" эл-ты перемешаны, но и валидные отсортированы неверно.
Вот так исправьте:
Код
C++ (Qt)
struct MyStruct {
MyStruct( int val = 0 ) : mUseText(false), mVal(val) {}
MyStruct( const char * str ) : mUseText(true), mStr(str) {}
 
bool operator < ( const MyStruct & sec ) const
{
return toInt() < sec.toInt();
}
 
int toInt() const
{
return  mUseText? std::numeric_limits<int>::max() : mVal;
}
 
// data
bool mUseText;
union {
int mVal;
const char * mStr;
};
};
 

А с визитером будет еще проще.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #52 : Февраль 18, 2015, 15:24 »

... Может небольшую статью об этом патерне написать?)
С удовольствием почитаю - уж лучше Вы будете писать чем некоторые  Улыбающийся

Вот так исправьте:
Вот в том-то и дело что это прямая ошибка которую надо исправлять.

2m_ax Ну хорошо , вот у меня есть Ваш код и нужно поддерживать еще тип, напр short. Я должен
Цитировать
template <class T>
struct numeric_visitor : public boost::static_visitor<T>
{
    T operator()(int val) const { return val; }
    T operator()(short val) const { return val; }  // добавить здесь

Цитировать
variant_visitor<boost::optional<double> (variant_t)> is_numeric;
    is_numeric.reg<int>([](variant_t v) { return v.get<int>(); });
    is_numeric.reg<short>([](variant_t v) { return v.get<short>(); });  // добавить здесь
И может где-то еще. Поясните, чем это отличается от редактирования "простыни" if-else-switch? Все равно "перебор", т.е. все типы должны быть явно указаны и все операции извлечения значения должны быть явно выписаны. "Зато" остатки моска выносятся бустовсими классами. А где же "выйгрыш"? Улыбающийся
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4349



Просмотр профиля
« Ответ #53 : Февраль 18, 2015, 15:54 »

Вот в том-то и дело что это прямая ошибка которую надо исправлять.
Вот я ее и исправил, в моем варианте все правильно сравнивается и сортируется, точнее согласно, выведенному нами выше, допущению: числа в правильном порядке впереди, строки в конце.
« Последнее редактирование: Февраль 19, 2015, 08:41 от Old » Записан
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2094



Просмотр профиля
« Ответ #54 : Февраль 19, 2015, 14:32 »

Цитировать
Поясните, чем это отличается от редактирования "простыни" if-else-switch? Все равно "перебор", т.е. все типы должны быть явно указаны и все операции извлечения значения должны быть явно выписаны.
Нет, в том то и дело что для boost::variant/ boost::static_visitor это не обязательно..
Если я хочу извлечь число то достаточно написать пару строк:
Код
C++ (Qt)
typedef boost::variant<float, double, int, long, short, std::string> variant_t;
 
struct numeric : public boost::static_visitor<boost::optional<double>>
{
   template <class T>
   result_type operator()(T v) const
   {
       return std::is_arithmetic<T>::value ? v : result_type();
   }
 
   result_type operator()(const std::string & s) const
   {
       try {
           return std::stod(s);
       } catch (...) {
           return result_type();
       }
   }
};
 

Всё!
Вы же будете писать аналогичную функцию numeric, (которую мы уже видели) где будет вся это простыня с проверками на ВСЕ типы + ещё попытка преобразовать строку в число..



  
« Последнее редактирование: Февраль 19, 2015, 15:40 от m_ax » Записан

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

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

Сообщений: 11445


Просмотр профиля
« Ответ #55 : Февраль 20, 2015, 11:43 »

Нет, в том то и дело что для boost::variant/ boost::static_visitor это не обязательно..
Если я хочу извлечь число то достаточно написать пару строк:
Код
C++ (Qt)
typedef boost::variant<float, double, int, long, short, std::string> variant_t;
 
struct numeric : public boost::static_visitor<boost::optional<double>>
{
   template <class T>
   result_type operator()(T v) const
   {
       return std::is_arithmetic<T>::value ? v : result_type();
   }
...
};
 

Всё!
Ну так и я могу, напр
Код
C++ (Qt)
QVariant v(5);
if (v.canConvert<double>())
double d = v.toDouble();
 
И как-то выглядит приятнее Улыбающийся Но за этим та же самая "простыня", уже кем-то написанная.

Интересен такой момент: вот я, допустим, хочу создать MyClass "по образцу" и определил набор типов
Код
C++ (Qt)
typedef MyClass<float, double, int, long, short, std::string> ClassNum;
 
Что теперь - я могу извлекать/получать значения "непосредственно", напр
Код
C++ (Qt)
void Test( const ClassNum & src )
{
 double d = src;
 short s = src;      // неясно, а если src не влезет в short?
 std:::string s = src;
//  long long l = src; // а этого типа нет для ClassNum
}  
 
Или так нельзя, нужно только через визитор?
Спасибо
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4349



Просмотр профиля
« Ответ #56 : Февраль 20, 2015, 12:54 »

Ну так и я могу, напр
Это не равнозначный код.

Что теперь - я могу извлекать/получать значения "непосредственно", напр
Код
C++ (Qt)
void Test( const ClassNum & src )
{
 double d = src;
 short s = src;      // неясно, а если src не влезет в short?
 std:::string s = src;
//  long long l = src; // а этого типа нет для ClassNum
}  
 

Можно сделать функцию, например, get, которая и будет конвертировать ClassNum во что скажете:
Код
C++ (Qt)
void Test( const ClassNum & src )
{
 double d = get<double>( src );
 short s = get<short>( src );      // Если не будет влазить в short - обрежется, а можно и исключение бросать об этом
 std::string str = get<std::string>( src );
 long long l = get<long long>( src ); // И не важно, что этого типа нет в варианте
 
 // или второй вариант get
 double d;
 get( d, src );
 short s;
 get( s, src );
 long long l;
 get( l, src );
}
 
« Последнее редактирование: Февраль 20, 2015, 13:28 от Old » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #57 : Февраль 20, 2015, 15:36 »

Можно сделать функцию, например, get, которая и будет конвертировать ClassNum во что скажете:
А какая техника изготовления таких ф-ций? Я полагаю что опять-таки сохранить тип в ClassNum явно и при извлечении "перебор". Или как-то все таки можно накрутить через enable_if и.т.п. ?
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4349



Просмотр профиля
« Ответ #58 : Февраль 20, 2015, 15:38 »

А какая техника изготовления таких ф-ций? Я полагаю что опять-таки сохранить тип в ClassNum явно и при извлечении "перебор". Или как-то все таки можно накрутить через enable_if и.т.п. ?

Код
C++ (Qt)
typedef boost::variant<short, int, long, float, double, std::string> var_t;
 
template<class T>
struct numeric : public boost::static_visitor<T>
{
template<class V>
T operator()( V val ) const
{
return std::is_arithmetic<V>::value ? val : std::numeric_limits<T>::max();
}
 
T operator()( const std::string &val ) const
{
try { return boost::lexical_cast<T>( val ); } catch(...) {}
return std::numeric_limits<T>::max();
}
};
 
struct literal : public boost::static_visitor<std::string>
{
template<class V>
std::string operator()( V val ) const
{
return boost::lexical_cast<std::string>( val );
}
};
 
template<class T>
T get( var_t v )
{
static_assert( std::is_arithmetic<T>::value, "Shit type T" );
return boost::apply_visitor( numeric<T>(), v );
}
 
template<>
std::string get<std::string>( var_t v )
{
return boost::apply_visitor( literal(), v );
}
 
template<class T>
void get( T &res, var_t v )
{
static_assert( std::is_arithmetic<T>::value, "Shit type T" );
res = boost::apply_visitor( numeric<T>(), v );
}
 
int main()
{
var_t v1 = 100000;
var_t v2 = 200000L;
var_t v3 = "300000";
var_t v4 = "test string";
 
ushort r1 = get<ushort>( v1 );
long r2 = get<long>( v2 );
double r3 = get<double>( v2 );
long long r4 = get<long long>( v1 );
float r5 = get<float>( v3 );
short r6 = get<short>( v3 );
std::string r7 = get<std::string>( v4 );
 
cout << "r1 (short) = " << r1 << endl
<< "r2 (long) = " << r2 << endl
<< "r3 (double) = " << r3 << endl
<< "r4 (long long) = " << r4 << endl
<< "r5 (float) = " << r5 << endl
<< "r6 (short) = " << r6 << endl
<< "r7 (string) = " << r7 << endl;
 
return 0;
}
 

Цитировать
r1 (short) = 34464
r2 (long) = 200000
r3 (double) = 200000
r4 (long long) = 100000
r5 (float) = 300000
r6 (short) = 32767
r7 (string) = test string
« Последнее редактирование: Февраль 20, 2015, 15:41 от Old » Записан
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2094



Просмотр профиля
« Ответ #59 : Февраль 22, 2015, 18:10 »

navrovsky, в вашей реализации Variant, в методе tryGetValue нужно вначале сделать detach, иначе через этот метод можно изменять значения других объектов Variant
т.е.:
Код
C++ (Qt)
   Variant v1 = 10;
   Variant v2 = v1;  // у v2 и v1 теперь общие  данные
 
   int *a;
 
   v1.tryGetValue(a);
 
   std::cout << *a << std::endl;
 
   (*a) = 123;  
 
   std::cout << v2.value<int>() << std::endl; // v2 теперь тоже вернёт 123
 

Можно исправить так, например:
Код
C++ (Qt)
template <typename T>
   bool tryGetValue(T*& val)
   {
       if (!isType<T>())
           return false;
 
       if (!holder_.unique())
           holder_ = std::make_shared<Holder<T>>(typeid(T).name(), std::static_pointer_cast<Holder<T>>(holder_)->value);
 
       val = &(std::static_pointer_cast<Holder<T>>(holder_)->value);
       return true;
   }
 
« Последнее редактирование: Февраль 22, 2015, 18:17 от m_ax » Записан

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

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


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