问题描述
在使用可变参数模板定义语法时,我面临着一个问题.
I'm facing with an issue in defining a grammar with variadic templates.
我首先定义了一些包含在某些结构(例如纬度,经度)中的简单语法,如下所示:
I started by defining some simple grammars contained into some struct (e.g. Latitude, Longitude) as follows:
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/variant.hpp>
#include <iostream>
#include <string>
using namespace boost::spirit;
template <class Attribute>
using command_rule =
qi::rule<std::string::iterator, Attribute, ascii::space_type>;
template <class Attribute>
using command_grammar =
qi::grammar<std::string::iterator, Attribute, ascii::space_type>;
struct Latitude {
struct return_type {
double lat_;
};
struct grammar : command_grammar<return_type()> {
grammar() : grammar::base_type{latitude_} {
latitude_ = "LAT=" >> qi::double_;
}
private:
command_rule<return_type()> latitude_;
};
};
BOOST_FUSION_ADAPT_STRUCT(Latitude::return_type, (double, lat_))
struct Longitude {
struct return_type {
double lon_;
};
struct grammar : command_grammar<return_type()> {
grammar() : grammar::base_type{longitude_} {
longitude_ = "LON=" >> qi::double_;
}
private:
command_rule<return_type()> longitude_;
};
};
BOOST_FUSION_ADAPT_STRUCT(Longitude::return_type, (double, lon_))
然后,我想将它们组合成一个完整的语法,该语法能够解析属于这些简单语法中的任何一个的字符串.为此,我定义了一个可变参数模板结构,该结构试图将子语法列表扩展为诸如"grammar1 | grammar2 | ..."之类的表达式.
Then, I would like to combine them in a complete grammar that is able to parse strings belonging to any of these simple grammars. To to this, I have defined a variadic template struct that tries to expand a list of sub-grammars into an expression like "grammar1 | grammar2 | ..."
template <class... Commands>
struct device_grammar : boost::spirit::qi::grammar<
std::string::iterator,
boost::variant<typename Commands::return_type...>(),
boost::spirit::ascii::space_type> {
typedef boost::variant<typename Commands::return_type...> return_type;
device_grammar() : device_grammar::base_type{rule_}{
build_rule<typename Commands::grammar...>();
}
private:
template <class CommandGrammar> void build_rule() {
rule_ = CommandGrammar();
}
template <class FirstGrammar, class SecondGrammar, class... Others>
void build_rule() {
build_rule<SecondGrammar, Others...>();
rule_ = rule_ | FirstGrammar();
}
boost::spirit::qi::rule<std::string::iterator, return_type(),
boost::spirit::ascii::space_type>
rule_;
};
typedef device_grammar<Latitude, Longitude> CoordinatesGrammar;
代码会编译(请参见下面的完整示例);问题在于,当它尝试解析输入字符串时,会产生分段错误.有人可以帮我解决此问题吗?
The code compiles (see the complete example below); the problem is that when it try to parse the input string, a segmentation fault is generated.Can someone please help me to fix this issue?
非常感谢.
示例代码(g ++-4.9或clang ++-3.9):
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/variant.hpp>
#include <iostream>
#include <string>
template <class... Commands>
struct device_grammar : boost::spirit::qi::grammar<
std::string::iterator,
boost::variant<typename Commands::return_type...>(),
boost::spirit::ascii::space_type> {
typedef boost::variant<typename Commands::return_type...> return_type;
device_grammar() : device_grammar::base_type{rule_}{
build_rule<typename Commands::grammar...>();
}
private:
template <class CommandGrammar> void build_rule() {
rule_ = CommandGrammar();
}
template <class FirstGrammar, class SecondGrammar, class... Others>
void build_rule() {
build_rule<SecondGrammar, Others...>();
rule_ = rule_ | FirstGrammar();
}
boost::spirit::qi::rule<std::string::iterator, return_type(),
boost::spirit::ascii::space_type>
rule_;
};
using namespace boost::spirit;
template <class Attribute>
using command_rule =
qi::rule<std::string::iterator, Attribute, ascii::space_type>;
template <class Attribute>
using command_grammar =
qi::grammar<std::string::iterator, Attribute, ascii::space_type>;
struct Latitude {
struct return_type {
double lat_;
};
struct grammar : command_grammar<return_type()> {
grammar() : grammar::base_type{latitude_} {
latitude_ = "LAT=" >> qi::double_;
}
private:
command_rule<return_type()> latitude_;
};
};
BOOST_FUSION_ADAPT_STRUCT(Latitude::return_type, (double, lat_))
struct Longitude {
struct return_type {
double lon_;
};
struct grammar : command_grammar<return_type()> {
grammar() : grammar::base_type{longitude_} {
longitude_ = "LON=" >> qi::double_;
}
private:
command_rule<return_type()> longitude_;
};
};
BOOST_FUSION_ADAPT_STRUCT(Longitude::return_type, (double, lon_))
typedef device_grammar<Latitude, Longitude> CoordinatesGrammar;
struct print : public boost::static_visitor<> {
void operator()(Latitude::return_type &t) const {
std::cout << "Latitude = " << t.lat_ << " deg" << std::endl;
;
}
void operator()(Longitude::return_type &t) const {
std::cout << "Longitude = " << t.lon_ << " deg" << std::endl;
;
}
};
int main() {
std::string s;
CoordinatesGrammar g;
CoordinatesGrammar::return_type v;
while (1) {
std::getline(std::cin, s);
auto it = s.begin();
if (qi::phrase_parse(it, s.end(), g, ascii::space, v)) {
print p;
boost::apply_visitor(p, v);
}
}
return 0;
}
据我了解,问题出在
As far as I understand, the problem is in the lines
rule_ = CommandGrammar();
...
rule_ = rule_ | FirstGrammar();
似乎语法对象不能是临时的,必须存储为类的成员.我该怎么办?
It seems that the grammar objects can not be temporary and have to be stored as members of the class. How can I do that?
我也曾尝试将此类对象存储在std::tuple
中,但似乎仍然无法正常工作.
I have also tried to store such objects in a std::tuple
, but it seems still not working.
推荐答案
您创建的内容与qi的自动解析器已完成的操作非常相似:"> http://www.boost.org/doc/libs/1_64_0/libs/spirit/doc/html/spirit/qi/reference/auto.html
What you are creating closely resembles what qi's auto parser already does: http://www.boost.org/doc/libs/1_64_0/libs/spirit/doc/html/spirit/qi/reference/auto.html
如果您专门研究 create_parser<>
用于数据类型,您可以直接使用qi::auto_
:
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>
namespace Commands {
namespace qi = boost::spirit::qi;
template <class T> using Rule = qi::rule<std::string::const_iterator, T()>;
template <typename... T>
auto parse(std::string const& s) {
boost::variant<T...> v;
auto it = s.begin();
if (qi::parse(it, s.end(), qi::auto_, v))
return v;
throw std::runtime_error(std::string(__FUNCTION__) + " failed");
}
}
struct Latitude { double lat_; };
BOOST_FUSION_ADAPT_STRUCT(Latitude, lat_)
struct Longitude { double lon_; };
BOOST_FUSION_ADAPT_STRUCT(Longitude, lon_)
namespace boost { namespace spirit { namespace traits {
template <> struct create_parser<Latitude> {
using type = Commands::Rule<Latitude>;
static type const& call() {
static type const s_rule = qi::skip(qi::space)["LAT=" >> qi::auto_];
return s_rule;
};
};
template <> struct create_parser<Longitude> {
using type = Commands::Rule<Longitude>;
static type const& call() {
static type const s_rule = qi::skip(qi::space)["LON=" >> qi::auto_];
return s_rule;
};
};
} } }
struct print {
using result_type = void;
void operator()(Latitude const &t) const { std::cout << "Latitude = " << t.lat_ << " deg" << std::endl; }
void operator()(Longitude const &t) const { std::cout << "Longitude = " << t.lon_ << " deg" << std::endl; }
};
#include <sstream>
int main() {
std::istringstream iss("LAT=4.3\n LON=5.0");
std::string s;
print printer;
while (std::getline(iss, s)) try {
auto v = Commands::parse<Latitude, Longitude>(s);
boost::apply_visitor(printer, v);
}
catch (std::exception const& e) {
std::cout << "'" << s << "': " << e.what() << "\n";
}
}
打印
Latitude = 4.3 deg
Longitude = 5 deg
更美好的事情
如果您不使用qi::rule<>
,则也不需要对迭代器进行硬编码.让我们进入完全有趣的模式并摆脱访客:
Nicer things
If you don't use qi::rule<>
you don't need to hard-code the iterator either. Let's go full fun mode and get rid of the visitor too:
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>
namespace Commands {
template <typename... T>
auto parse(std::string const& s) {
boost::variant<T...> v;
auto it = s.begin();
if (boost::spirit::qi::parse(it, s.end(), boost::spirit::qi::auto_, v))
return v;
throw std::runtime_error(std::string(__FUNCTION__) + " failed");
}
struct Latitude { double lat_; };
struct Longitude { double lon_; };
static inline std::ostream& operator<<(std::ostream& os, Latitude const &t) { return os << "Latitude = " << t.lat_ << " deg"; }
static inline std::ostream& operator<<(std::ostream& os, Longitude const &t) { return os << "Longitude = " << t.lon_ << " deg"; }
}
BOOST_FUSION_ADAPT_STRUCT(Commands::Latitude, lat_)
BOOST_FUSION_ADAPT_STRUCT(Commands::Longitude, lon_)
namespace boost { namespace spirit { namespace traits {
#define MAP_PARSER(T, expr) \
template <> struct create_parser<T> { \
using type = decltype(qi::attr_cast<T, T>(qi::copy(expr))); \
static type const& call() { static type const s_rule = qi::attr_cast<T, T>(qi::copy(expr)); return s_rule; }; \
};
#define AUTO_MAP_PARSER(T, caption) MAP_PARSER(T, qi::skip(qi::space)[qi::lit(caption) >> '=' >> qi::auto_])
AUTO_MAP_PARSER(::Commands::Longitude, "LON")
AUTO_MAP_PARSER(::Commands::Latitude, "LAT")
} } }
#include <sstream>
int main() {
std::istringstream iss("LAT=4.3\n LON=5.0");
std::string s;
while (std::getline(iss, s)) try {
using namespace Commands;
std::cout << "Parsed '" << s << "' into " << parse<Latitude, Longitude>(s) << "\n";
} catch (std::exception const& e) {
std::cout << "'" << s << "': " << e.what() << "\n";
}
}
打印
Parsed 'LAT=4.3' into Latitude = 4.3 deg
Parsed ' LON=5.0' into Longitude = 5 deg
这篇关于boost :: spirit :: qi ::语法和可变参数模板的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!