在 Boost.Spirit 中,为什么向量(包裹在结构中)需要融合包装器,而不是变体?
In Boost.Spirit, why is a fusion wrapper required for a vector (wrapped in a struct), but not a variant?
我想了解使用 Boost.Spirit 封装struct
时需要BOOST_FUSION_ADAPT_STRUCT
的确切场景。
下面是两个例子。 一个例子是(仅)具有variant
数据成员的单成员struct
。 此版本不需要将结构包装在 Fusion 容器中的BOOST_FUSION_ADAPT_STRUCT
宏。 构造函数足以让 Spirit 根据传入的 rhs 实例化/填充属性。
(请参阅代码中的注释,以了解我认为由于属性折叠规则而导致的 Boost.Spirit 为规则定义的 rhs 生成的属性类型。
第二个示例是(仅)具有vector
数据成员的单成员struct
。 即使构造函数定义为允许 Spirit 基于 rhs 填充属性,它也无法在没有BOOST_FUSION_ADAPT_STRUCT
的情况下编译。
为什么会有差异?我想了解为什么在第一种情况下,可以使用构造函数来填充属性(struct
),而在第二种情况下,构造函数是不够的,必须使用BOOST_FUSION_ADAPT_STRUCT
。
上面提到的例子如下。
示例 1:变体
#include <string>
#include <vector>
#include <boost/variant.hpp>
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
typedef std::string::const_iterator It;
using intermediate = boost::variant<std::string, int>;
// Simple parser demonstrating successful build with 'works_great'
struct works_great // No need for BOOST_FUSION_ADAPT_STRUCT - whoopee!
// But why - even given the constructor??
{
intermediate i;
works_great() = default;
works_great(intermediate i) : i{i} {}
};
// Not required for 'works_great' - constructors work just fine
//BOOST_FUSION_ADAPT_STRUCT(works_great, v)
struct parser : qi::grammar<It, works_great()>
{
parser() : parser::base_type(works_great)
{
using namespace qi;
intermediate = qi::string("test") | qi::int_;
// rhs should have attribute of type 'variant',
// matching the constructor
works_great = '{' >> intermediate >> '}';
}
private:
qi::rule<It, intermediate()> intermediate;
qi::rule<It, works_great()> works_great;
};
int main()
{
// The following all compiles/builds just fine
// (I don't care about the actual runtime results).
static const parser p;
works_great wg;
std::string const data {"{test}"};
auto f(begin(data)), l(end(data));
qi::parse(f,l,p,wg);
}
示例 2:矢量
#include <string>
#include <vector>
#include <boost/variant.hpp>
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
typedef std::string::const_iterator It;
// We need BOOST_FUSION_ADAPT_STRUCT for this one, but not for the above.
// Constructors don't help. Only difference seems to be
// the vector (rather than variant).
struct not_so_much // not so much - unless BOOST_FUSION_ADAPT_STRUCT is used
{
std::vector<int> s;
// Constructors do not help here
//not_so_much() = default;
//not_so_much(std::vector<int> s) : s{std::move(s)} {}
};
// Required for 'not_so_much' - constructors don't work
BOOST_FUSION_ADAPT_STRUCT(not_so_much, s)
// Simple parser demonstrating successful build with 'not_so_much' -
// but only when BOOST_FUSION_ADAPT_STRUCT is used.
struct parser : qi::grammar<It, not_so_much()>
{
parser() : parser::base_type(not_so_much)
{
using namespace qi;
// Note: I know that 'eps' is required, below, to compile the
// single-member struct successfully
// rhs should have attribute of type 'vector<int>',
// matching the constructor as well...
// but it doesn't work.
not_so_much = eps >> (qi::int_ % "|");
}
private:
qi::rule<It, not_so_much()> not_so_much;
};
int main()
{
// The following all compiles/builds just fine
static const parser p;
not_so_much nm;
std::string const data {"5|9|16"};
auto f(begin(data)), l(end(data));
qi::parse(f,l,p,nm);
}
区别是双重的:
- 该属性不是容器
- 默认构造函数允许将合成属性隐式转换为公开属性
后一种差异,你已经注意到了。第一:没那么多。
真正有原则的答案是:
Qi属性传播是一种启发式机器。
可悲的是,很少有东西可以优化性能(X3 做得更好)。例外的关键领域之一是增量解析到容器中(甚至跨多个规则)¹。
这很有意义(因为即使例如逐个字符构建字符串也会非常慢......但它确实会导致意外(例如,在输出上重复解析 boost::spirit::qi,了解 Boost.spirit 的字符串解析器)
¹(实际上也是非容器,但我跑题了。我不认为没有语义动作就无法播放)
一些不必要的体操
您实际上可以稍微更改属性传播触发的时间,并且无需适应,尽管我建议不要这样做:只是适应更加一致和自我描述:
住在科里鲁
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
namespace Ast {
using vec = std::vector<int>;
struct not_so_much {
vec s;
not_so_much() = default;
not_so_much(vec s) : s(std::move(s)) {}
};
}
typedef std::string::const_iterator It;
typedef qi::rule<It, Ast::not_so_much()> Parser;
template <typename Expr> void do_test(Expr const& expression) {
Parser const p = expression;
Ast::not_so_much nm;
std::string const data {"5|9|16"};
It f = begin(data), l = end(data);
if (qi::parse(f,l,p,nm)) {
std::cout << "Parsed " << nm.s.size() << " elements: ";
copy(nm.s.begin(), nm.s.end(), std::ostream_iterator<int>(std::cout, " "));
std::cout << "n";
} else {
std::cout << "Parse failedn";
}
if (f != l)
std::cout << "Remaining unparsed: '" << std::string(f,l) << "'n";
}
int main() {
using namespace qi;
do_test(attr_cast<Ast::not_so_much, Ast::vec>(int_ % '|'));
do_test(attr_cast<Ast::not_so_much>(int_ % '|'));
do_test(as<Ast::vec>()[int_ % '|']);
}
指纹
Parsed 3 elements: 5 9 16
Parsed 3 elements: 5 9 16
Parsed 3 elements: 5 9 16
- 如何在c++17中制作一个模板包装器/装饰器
- std::vector的包装器,使数组的结构看起来像结构的数组
- 如何在c++迭代器类型中包装std::chrono
- 是否可以用"iostream"包装现有的TCP/OOpenSSL会话
- 用pybind11包装C++抽象类时出错
- 为左值和右值的包装器实现C++范围
- C结构,其指针将被包装在unique_ptr中
- 如何包装第三方DLL在R中使用
- 在类型和包装器之间reinterpret_cast是否安全<Type>?
- 将 N-arg 函数包装到另一个函数中
- 元组由 Swig 生成的 Python 包装器返回,用于C++向量
- 包装一个对象并假装它是一个 int
- 使用 Python Extension API 包装复杂C++类
- 外壳包装器句柄/执行交互式命令管道C++ UNIX
- 包装C++类时不完整的类型 GLFWwindow
- 将函数包装器转换为 std::function
- C++函数包装器来捕获某些信号
- 创建包装升压适配器的自定义范围类
- 在 Boost.Spirit 中,为什么向量(包裹在结构中)需要融合包装器,而不是变体?
- 包装助推.融合序列