问。为什么这么复杂[...]
通过标记类型 (rule-id) 将规则定义静态链接到规则的机制很棘手。它实际上取决于 parse_rule¹ 函数模板的特殊化。
但是,函数模板依赖于:
- 规则 ID(“标签类型”)
- 迭代器类型
- 上下文(包括诸如skipper或
with<>指令之类的东西)
所有类型都必须完全匹配。这是一个常见的错误来源。
Q. [...] 而如果我删除 parser2 中的规则,一切正常吗?
可能是因为规则定义对编译器可见以在该点进行实例化,或者是因为类型与刚才描述的匹配。
稍后我会查看您的具体代码。
复制
阅读编译器消息
我的编译器使用 -DTEST1 发出警告:
unit1.h|13 col 5| warning: ‘bool unit1::parse_rule(unit1::parser1_t, Iterator&, const Iterator&, const Context&, boost::spirit::x3::rule<unit1::u1, long unsigned int>::attribute_type&) [with Iterator = boost::spirit::basic_istream_iterator<char>; Context = boost::spirit::x3::context<main()::u2, const boost::spirit::x3::sequence<boost::spirit::x3::literal_string<const char*, boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type>, boost::spirit::x3::rule<unit1::u1, long unsigned int> >, boost::spirit::x3::context<boost::spirit::x3::skipper_tag, const boost::spirit::x3::char_class<boost::spirit::char_encoding::ascii, boost::spirit::x3::space_tag>, boost::spirit::x3::unused_type> >]’ used but never defined
这会将模板特化的 exact 类型参数拼写为 TU 中的 explicitly-instantiate。
链接器错误拼写缺少的符号:
/home/sehe/custom/spirit/include/boost/spirit/home/x3/nonterminal/rule.hpp:135: undefined reference to bool
unit1::parse_rule<:spirit::basic_istream_iterator std::char_traits>, boost::spirit::x3::context<:u2 boost::spirit::x3::sequence const boost::spirit::char_encoding::standard boost::spirit::x3::unused_type>, boost::spirit::x3::rule<:u1> > 常量,
boost::spirit::x3::context<:spirit::x3::skipper_tag boost::spirit::x3::char_class boost::spirit::x3::space_tag> const, boost::spirit::x3::unused_type> >
(boost::spirit::x3::rule<:u1 unsigned long false>, boost::spirit::basic_istream_iterator&,
boost::spirit::basic_istream_iterator
const&, boost::spirit::x3::context<:u2 boost::spirit::x3::sequence const boost::spirit::char_encoding::standard boost::spirit::x3::unused_type>, boost::spirit::x3::rule<:u1> > 常量,
boost::spirit::x3::context<:spirit::x3::skipper_tag boost::spirit::x3::char_class boost::spirit::x3::space_tag> 常量,boost::spirit::x3::unused_type> >
const&, unsigned long&)'`
总而言之,您的任务就是比较它们 (!!) 并注意差异。
阅读宏魔法
扩展宏获取
template <typename Iterator, typename Context> inline bool parse_rule( decltype(parser1) , Iterator& first, Iterator const& last , Context const& context, decltype(parser1)::attribute_type& attr) { using boost::spirit::x3::unused; static auto const def_ = (parser1 = parser1_def); return def_.parse(first, last, context, unused, attr); }
template bool parse_rule<iter_t, context_t>( parser1_t rule_ , iter_t& first, iter_t const& last , context_t const& context, parser1_t::attribute_type&);
这是为 ...DEFINE:
template <typename Iterator, typename Context>
inline bool parse_rule(decltype(parser1), Iterator& first,
Iterator const& last, Context const& context,
decltype(parser1)::attribute_type& attr)
{
using boost::spirit::x3::unused;
static auto const def_ = (parser1 = parser1_def);
return def_.parse(first, last, context, unused, attr);
}
对于显式的 ...INSTANTIATE:
template bool parse_rule<iter_t, context_t>(parser1_t rule_, iter_t& first,
iter_t const& last, context_t const& context,
parser1_t::attribute_type&);
替换类型可以准确显示实例化的内容(请参阅上面的警告)。
其他选项
不用费力,我们知道哪些模板类型参数可能是错误的,所以让我们检查一下:
-
迭代器:
static_assert(std::is_same_v<iter_t, boost::spirit::istream_iterator>);
iter_t b{i >> std::noskipws}, e {};
编译器确认这不是罪魁祸首。
-
船长应该是x3::ascii::space_type,这似乎也很匹配。
-
问题一定是上下文。现在让我们从链接器错误中提取上下文:
bool unit1::parse_rule<...> >
(x3::rule<unit1::u1, unsigned long, false>, iter_t &, iter_t const &,
// this is the context:
x3::context<
main::u2,
x3::sequence<x3::literal_string<char const *,
boost::spirit::char_encoding::standard,
x3::unused_type>,
x3::rule<unit1::u1, unsigned long, false>> const,
x3::context<x3::skipper_tag,
x3::char_class<boost::spirit::char_encoding::ascii,
x3::space_tag> const,
x3::unused_type>> const &,
// this is the attribtue
unsigned long &);
看起来上下文并不是我们所期望的。我认为问题在于 rule2 定义是“可见的”,导致包含该定义的上下文(这是允许本地 x3::rule 定义完全没有定义宏魔法的机制)。
我记得最近有一篇邮件列表帖子指出了这一点(当时我有点惊讶):
https://sourceforge.net/p/spirit/mailman/message/37194823/
1 月 5 日下午 13:12,Larry Evans 写道:
但是,使用 BOOST_SPIRIT_DEFINE 还有另一个原因。什么时候
递归规则很多,BOOST_SPIRIT_DEFINE 没有
使用,这会导致更重的模板处理和伴随
编译时间慢。原因是,没有 BOOST_SPIRIT_DEFINE,
规则的定义存储在上下文中,这就是
导致编译时间爆炸。
因此,当您发现添加时编译时间变慢时,请注意这一点
更多递归规则。
感谢您指出这一点。我在没有意识到的情况下遇到了这个
省略定义分离是一个关键因素。
那么我想它也可以在某些情况下提供缓解
规则改变时导致极端的模板递归 skipper
(因为上下文在技术上一直不同)。
同样,这实际上是一个非常有用的注释。谢谢。
赛斯
在线程的前面,我表达了为什么我不喜欢宏机制并且从不跨 TU 传播我的 X3 规则的原因。到现在为止,您可能会欣赏这种情绪:)
解决方法
您可以通过制造正确的上下文类型并实例化它(以及):(unit1.h)
struct u2;
using context2_t = x3::context<
u2,
decltype("" >> parser1_t{}) const,
context_t>;
BOOST_SPIRIT_DECLARE(parser1_t)
在cpp中:
BOOST_SPIRIT_DEFINE(parser1)
BOOST_SPIRIT_INSTANTIATE(parser1_t, iter_t, context_t) // optionally
BOOST_SPIRIT_INSTANTIATE(parser1_t, iter_t, context2_t)
毫不奇怪,这有效:https://wandbox.org/permlink/Y6NsKCcIDgiHGJf2
总结
令我惊讶的是,我又一次学会了不喜欢 X3 的规则分离魔法的理由。不过,如果你需要,你可能不应该混搭,而是定义parser2 out-of-line。
namespace unit2 {
parser2_t parser2 = "unit2_rule";
auto const parser2_def = "Trace address: " >> parser1();
BOOST_SPIRIT_DEFINE(parser2)
BOOST_SPIRIT_INSTANTIATE(parser2_t, iter_t, context_t)
} // namespace unit2
再次查看Live On Wandbox
完整列表
Wandbox的后代:
-
文件unit1.cpp
#include "unit1.h"
namespace unit1 {
parser1_t parser1 = "unit1_rule";
auto const parser1_def = x3::uint_;
BOOST_SPIRIT_DEFINE(parser1)
BOOST_SPIRIT_INSTANTIATE(parser1_t, iter_t, context_t)
} // namespace unit1
unit1::parser1_t const &parser1() { return unit1::parser1; }
-
文件unit1.h
#ifndef UNIT1_H
#define UNIT1_H
#include "boost/spirit/home/x3.hpp"
#include "boost/spirit/include/support_istream_iterator.hpp"
#include <cstdint>
namespace x3 = boost::spirit::x3;
using iter_t = boost::spirit::istream_iterator;
using context_t = x3::phrase_parse_context<x3::ascii::space_type>::type;
namespace unit1 {
using parser1_t = x3::rule<class u1, std::uint64_t> const;
BOOST_SPIRIT_DECLARE(parser1_t)
} // namespace unit1
unit1::parser1_t const &parser1();
#endif /* UNIT1_H */
-
文件unit2.cpp
#include "unit2.h"
#include "unit1.h"
namespace unit2 {
parser2_t parser2 = "unit2_rule";
auto const parser2_def = "Trace address: " >> parser1();
BOOST_SPIRIT_DEFINE(parser2)
BOOST_SPIRIT_INSTANTIATE(parser2_t, iter_t, context_t)
} // namespace unit2
unit2::parser2_t const &parser2() { return unit2::parser2; }
-
文件unit2.h
#ifndef UNIT2_H
#define UNIT2_H
#include "boost/spirit/home/x3.hpp"
#include "boost/spirit/include/support_istream_iterator.hpp"
#include <cstdint>
namespace x3 = boost::spirit::x3;
using iter_t = boost::spirit::istream_iterator;
using context_t = x3::phrase_parse_context<x3::ascii::space_type>::type;
namespace unit2 {
using parser2_t = x3::rule<class u2, std::uint64_t> const;
BOOST_SPIRIT_DECLARE(parser2_t)
} // namespace unit2
unit2::parser2_t const &parser2();
#endif /* UNIT2_H */
-
文件main.cpp
#include "unit2.h"
#include <iostream>
namespace x3 = boost::spirit::x3;
int main() {
std::string input("Trace address: 123434");
std::istringstream i(input);
std::cout << "parsing: " << input << "\n";
static_assert(std::is_same_v<iter_t, boost::spirit::istream_iterator>);
iter_t b{i >> std::noskipws}, e {};
uint64_t addr = 0;
bool v = x3::phrase_parse(b, e, parser2(), x3::ascii::space, addr);
std::cout << "result: " << (v ? "OK" : "Failed") << "\n";
std::cout << "result: " << addr << "\n";
return v;
}