Получение boost::spirit::qi для использования контейнеров stl
Я пытаюсь разобрать что-то с помощью библиотеки qi boost.spirit, и у меня возникла проблема. Согласно духу документов, a >> b
должен производить что-то с типом tuple<A, B>
, Но это boost::tuple
(он же вектор слияния), а не std::tuple
(который я хочу).
Есть ли простой способ сделать это преобразование между boost::tuple
=>std::tuple
?
На той же странице документации написано, что *a
должен производить что-то с типом vector<A>
, Это, кажется, производит std::vector<A>
(или какой-то boost::vector<A>
которые могут неявно преобразовать в std::vector<A>
). Я просто хотел узнать, доступно ли такое же поведение для кортежей.
1 ответ
КОРОТКИЙ ОТВЕТ:
использование #include <boost/fusion/adapted/std_tuple.hpp>
,
БОЛЬШЕ ПОЛНОГО ОТВЕТА:
Как вы можете видеть здесь:
В таблицах атрибутов мы будем использовать
vector<A>
а такжеtuple<A, B...>
только в качестве заполнителей. Обозначениеvector<A>
обозначает любой контейнер STL, содержащий элементы типа A и обозначенияtuple<A, B...>
обозначает любую последовательность Boost.Fusion, содержащую элементы A, B, ... и т. д. Наконец, Unused означает unused_type.
Поэтому, когда парсер / генератор имеет атрибут tuple<A,B...>
Вы можете использовать любую последовательность слияния (например, fusion::vector или fusion::list) или все, что можно адаптировать к последовательности слияния (например, boost:: array, boost:: tuple, std:: pair, std:: кортеж, ваша собственная структура, использующая BOOST_FUSION_ADAPT_STRUCT).
И когда это vector<A>
Вы можете использовать std::vector, std::list и даже std:: map, если ваши элементы являются парами. Вы также можете использовать свою собственную структуру, если вы также специализируете несколько точек настройки (по крайней мере, is_container, container_value и push_back_container в boost::spirit::traits).
станд:: пара
Для того, чтобы иметь возможность использовать std::pair
с духом вам просто нужно добавить один заголовок:
#include <boost/fusion/include/std_pair.hpp>
...
qi::rule<Iterator,std::pair<int,double>()> rule =
qi::int_ >> qi::lit(',') >> qi::double_;
станд:: кортеж
Начиная с boost 1.48.0, вы можете сделать то же самое для std:: tuple:
#include <boost/fusion/adapted/std_tuple.hpp>
...
qi::rule<Iterator,std::tuple<int,std::string,double>()> rule =
qi::int_ >> qi::lit(',') >> +~qi::char_(',') >> qi::lit(',') >> qi::double_;
Ваша собственная структура
Вы можете очень легко адаптировать свою пользовательскую структуру с помощью BOOST_FUSION_ADAPT_STRUCT:
#include <boost/fusion/include/adapt_struct.hpp>
...
struct normal_struct
{
int integer;
double real;
};
BOOST_FUSION_ADAPT_STRUCT(
normal_struct,
(int, integer)
(double, real)
)
...
qi::rule<Iterator,normal_struct()> rule =
qi::int_ >> qi::lit(',') >> qi::double_;
Однако есть одно известное ограничение: когда вы пытаетесь использовать структуру, содержащую один элемент, который также является контейнером, компиляция завершится неудачно, если вы не добавите qi::eps >> ...
к вашему правилу.
struct struct_with_single_element_container
{
std::vector<int> cont;
};
BOOST_FUSION_ADAPT_STRUCT(
struct_with_single_element_container,
(std::vector<int>, cont)
)
...
qi::rule<Iterator,struct_with_single_element_container()> rule =
qi::eps >> qi::int_%qi::lit(',');
станд:: Карта
Вы можете просто использовать std:: map в качестве контейнера std:: pair. Имейте в виду, однако, что если в вашем вводе есть повторяющиеся клавиши, на карту будет вставлен только первый (если вы используете мультикарту, все будет вставлено, конечно):
#include <boost/fusion/include/std_pair.hpp>
...
qi::rule<std::string::const_iterator, std::pair<double,int>()> pair_rule =
qi::double_ >> qi::lit('=') >> qi::int_;
qi::rule<std::string::const_iterator, std::map<double,int>()> rule =
pair_rule%qi::lit(',');
//You can also use
//qi::rule<std::string::const_iterator, std::map<double,int>()> rule =
//(qi::double_ >> qi::lit('=') >> qi::int_)%qi::lit(',');
Ваша собственная структура как контейнер
Используя точки настройки Spirit, вы также можете заставить свою структуру вести себя так, как если бы она была контейнером при работе с атрибутами. Минимум, на который нужно специализироваться, это is_container
, container_value
а также push_back_container
, Вот пара примеров:
Первый довольно простой (и глупый). Это делает вашу структуру иметь атрибут, совместимый с std::vector<int>
, Каждый раз, когда анализируется int, он добавляется к сумме в аккумуляторе. Вы можете найти менее глупые подходы здесь и здесь (в "старом ответе").
struct accumulator
{
accumulator(): total(){}
int total;
};
namespace boost{ namespace spirit{ namespace traits
{
template<>
struct is_container<accumulator> : boost::mpl::true_
{};
template<>
struct container_value<accumulator>
{
typedef int type;
};
template<>
struct push_back_container<accumulator,int>
{
static bool call(accumulator& c, int val)
{
c.total+=val;
return true;
}
};
}}}
...
qi::rule<Iterator,accumulator()> rule =
qi::int_%qi::lit(',');
Второй немного сложнее (не сильно). Это делает вашу структуру иметь атрибут, совместимый с std::vector<boost::variant<int,std::string> >
, Когда int анализируется, он добавляется к ints
контейнер в распределителе, аналогично строки хранятся в strings
контейнер. Примеры использования этого ( 1, 2 и 3).
struct distributor
{
distributor():ints(),strings(){}
std::vector<int> ints;
std::vector<std::string> strings;
};
namespace boost{ namespace spirit{ namespace traits
{
template<>
struct is_container<distributor> : boost::mpl::true_
{};
template<>
struct container_value<distributor>
{
typedef boost::variant<int,std::string> type;
};
template<>
struct push_back_container<distributor,int>
{
static bool call(distributor& c, int val)
{
c.ints.push_back(val);
return true;
}
};
template<>
struct push_back_container<distributor,std::string>
{
static bool call(distributor& c, std::string const& val)
{
c.strings.push_back(val);
return true;
}
};
}}}
...
qi::rule<std::string::const_iterator, std::string()> string_rule =
+~qi::char_(',');
qi::rule<std::string::const_iterator, distributor()> rule =
(qi::int_ | string_rule)%qi::lit(',');
Все тесты в одном файле cpp
#include <iostream>
#include <string>
#include <utility>
#include <tuple>
#include <list>
#include <vector>
#include <map>
#include <boost/fusion/include/std_pair.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/variant.hpp>
namespace qi=boost::spirit::qi;
struct normal_struct
{
int integer;
double real;
};
struct struct_with_single_element_container
{
std::vector<int> cont;
};
BOOST_FUSION_ADAPT_STRUCT(
normal_struct,
(int, integer)
(double, real)
)
BOOST_FUSION_ADAPT_STRUCT(
struct_with_single_element_container,
(std::vector<int>, cont)
)
struct accumulator
{
accumulator(): total(){}
int total;
};
namespace boost{ namespace spirit{ namespace traits
{
template<>
struct is_container<accumulator> : boost::mpl::true_
{};
template<>
struct container_value<accumulator>
{
typedef int type;
};
template<>
struct push_back_container<accumulator,int>
{
static bool call(accumulator& c, int val)
{
c.total+=val;
return true;
}
};
}}}
struct distributor
{
distributor():ints(),strings(){}
std::vector<int> ints;
std::vector<std::string> strings;
};
namespace boost{ namespace spirit{ namespace traits
{
template<>
struct is_container<distributor> : boost::mpl::true_
{};
template<>
struct container_value<distributor>
{
typedef boost::variant<int,std::string> type;
};
template<>
struct push_back_container<distributor,int>
{
static bool call(distributor& c, int val)
{
c.ints.push_back(val);
return true;
}
};
template<>
struct push_back_container<distributor,std::string>
{
static bool call(distributor& c, std::string const& val)
{
c.strings.push_back(val);
return true;
}
};
}}}
int main()
{
{
std::pair<int,double> parsed;
qi::rule<std::string::const_iterator, std::pair<int,double>()> rule =
qi::int_ >> qi::lit(',') >> qi::double_;
std::string test="1,2.5";
std::string::const_iterator iter=test.begin(), end=test.end();
bool result = qi::parse(iter,end,rule,parsed);
if(result && iter==end)
{
std::cout << "Success." << std::endl;
std::cout << "First: " << parsed.first << ", Second: " << parsed.second << std::endl;
}
else
{
std::cout << "Failure." << std::endl;
std::cout << "Unparsed: " << std::string(iter,end) << std::endl;
}
}
{
std::tuple<int,std::string,double> parsed;
qi::rule<std::string::const_iterator, std::tuple<int,std::string,double>()> rule =
qi::int_ >> qi::lit(',') >> +~qi::char_(',') >> qi::lit(',') >> qi::double_;
std::string test="1,abc,2.5";
std::string::const_iterator iter=test.begin(), end=test.end();
bool result = qi::parse(iter,end,rule,parsed);
if(result && iter==end)
{
std::cout << "Success." << std::endl;
std::cout << "get<0>: " << std::get<0>(parsed) << ", get<1>: " << std::get<1>(parsed) << ", get<2>: " << std::get<2>(parsed) << std::endl;
}
else
{
std::cout << "Failure." << std::endl;
std::cout << "Unparsed: " << std::string(iter,end) << std::endl;
}
}
{
normal_struct parsed;
qi::rule<std::string::const_iterator, normal_struct()> rule =
qi::int_ >> qi::lit(',') >> qi::double_;
std::string test="1,2.5";
std::string::const_iterator iter=test.begin(), end=test.end();
bool result = qi::parse(iter,end,rule,parsed);
if(result && iter==end)
{
std::cout << "Success." << std::endl;
std::cout << "integer: " << parsed.integer << ", real: " << parsed.real << std::endl;
}
else
{
std::cout << "Failure." << std::endl;
std::cout << "Unparsed: " << std::string(iter,end) << std::endl;
}
}
{
struct_with_single_element_container parsed;
//there is a problem when you have a struct with a single element container, the workaround is simply adding qi::eps to the rule
qi::rule<std::string::const_iterator, struct_with_single_element_container()> rule =
qi::eps >> qi::int_%qi::lit(',');
std::string test="1,2";
std::string::const_iterator iter=test.begin(), end=test.end();
bool result = qi::parse(iter,end,rule,parsed);
if(result && iter==end)
{
std::cout << "Success." << std::endl;
std::cout << "[0]: " << parsed.cont[0] << ", [1]: " << parsed.cont[1] << std::endl;
}
else
{
std::cout << "Failure." << std::endl;
std::cout << "Unparsed: " << std::string(iter,end) << std::endl;
}
}
{
std::list<int> parsed;
qi::rule<std::string::const_iterator, std::list<int>()> rule =
qi::int_%qi::lit(',');
std::string test="1,2";
std::string::const_iterator iter=test.begin(), end=test.end();
bool result = qi::parse(iter,end,rule,parsed);
if(result && iter==end)
{
std::cout << "Success." << std::endl;
std::cout << "front: " << parsed.front() << ", back: " << parsed.back() << std::endl;
}
else
{
std::cout << "Failure." << std::endl;
std::cout << "Unparsed: " << std::string(iter,end) << std::endl;
}
}
{
std::map<double,int> parsed;
qi::rule<std::string::const_iterator, std::pair<double,int>()> pair_rule =
qi::double_ >> qi::lit('=') >> qi::int_;
qi::rule<std::string::const_iterator, std::map<double,int>()> rule =
pair_rule%qi::lit(',');
std::string test="2.5=1,3.5=2";
std::string::const_iterator iter=test.begin(), end=test.end();
bool result = qi::parse(iter,end,rule,parsed);
if(result && iter==end)
{
std::cout << "Success." << std::endl;
std::cout << "map[2.5]: " << parsed[2.5] << ", map[3.5]: " << parsed[3.5] << std::endl;
}
else
{
std::cout << "Failure." << std::endl;
std::cout << "Unparsed: " << std::string(iter,end) << std::endl;
}
}
{
accumulator parsed;
qi::rule<std::string::const_iterator, accumulator()> rule =
qi::int_%qi::lit(',');
std::string test="1,2,3";
std::string::const_iterator iter=test.begin(), end=test.end();
bool result = qi::parse(iter,end,rule,parsed);
if(result && iter==end)
{
std::cout << "Success." << std::endl;
std::cout << "total: " << parsed.total << std::endl;
}
else
{
std::cout << "Failure." << std::endl;
std::cout << "Unparsed: " << std::string(iter,end) << std::endl;
}
}
{
distributor parsed;
qi::rule<std::string::const_iterator, std::string()> string_rule =
+~qi::char_(',');
qi::rule<std::string::const_iterator, distributor()> rule =
(qi::int_ | string_rule)%qi::lit(',');
std::string test="abc,1,2,def,ghi,3,jkl";
std::string::const_iterator iter=test.begin(), end=test.end();
bool result = qi::parse(iter,end,rule,parsed);
if(result && iter==end)
{
std::cout << "Success." << std::endl;
std::cout << "ints" << std::endl;
for(auto val: parsed.ints)
std::cout << val << std::endl;
std::cout << "strings" << std::endl;
for(const auto& val: parsed.strings)
std::cout << val << std::endl;
}
else
{
std::cout << "Failure." << std::endl;
std::cout << "Unparsed: " << std::string(iter,end) << std::endl;
}
}
}