误用boost::phoenix::static_cast_获取占位符后对象的问题求助
我正在尝试使用boost::spirit::qi库及_1、_a这类占位符,目前难以访问boost::qi/phoenix占位符背后的底层Tag对象。
定义的Tag类如下:
class Tag { public: Tag() = default; // Needed by qi Tag(std::uint8_t _raw_tag) : m_raw_tag( _raw_tag ) {} std::uint8_t get_size() { return m_raw_tag & 0b111; } std::uint8_t get_type() { return m_raw_tag & 0b1000; } private: std::uint8_t m_raw_tag; };
我需要解析以标签字节开头的帧,该字节包含后续待读取数据的相关信息,因此编写Tag类来提取标签类型、数据大小等信息。由于数据大小可能为3字节(非预定义的1、2、4字节),我计划逐字节读取数据并通过位偏移存入std::uint32_t中,伪C++解析器代码如下:
template<typename _Iterator> struct Read_frame : qi::grammar<_Iterator, std::uint32_t(), qi::locals<std::uint8_t>> { Read_frame() : Read_frame::base_type(data_parser) { using boost::spirit::qi::byte_; using boost::spirit::qi::omit; using boost::spirit::qi::repeat; using boost::spirit::qi::_val; using namespace qi::labels; tag_parser %= byte_; // we read what's in the tag but we don't store it // Call the method get_size() of Tag is my issue, I don't know how to do it data_parser %= omit[tag_parser[ _a = _1.get_size()]] >> eps[_val = 0] >> repeat(_a)[ byte_[ _val += (_1 << (--_a * 8)) ]; } qi::rule<_Iterator, std::uint32_t(), qi::locals<std::uint8_t>> data_parser; qi::rule<_Iterator, Tag()> tag_parser; };
问题出在以下代码行:
data_parser %= omit[context_tag[ _a = _1.get_size()]] >> eps[_val = 0]
我不知如何在语义动作中调用Tag的get_size()方法,尝试使用boost::phoenix::static_cast_<Tag*>(&_1)->get_size()但无效。这是我首次使用boost::spirit和boost::phoenix,不太理解其占位符及boost::phoenix::static_cast_的工作原理,恳请提供帮助。
核心问题:Phoenix占位符的成员调用逻辑
Boost.Phoenix的占位符(如_1)是表达式模板对象,不能直接用.或->调用成员函数,必须通过Phoenix提供的绑定机制来处理。
步骤1:正确绑定Tag的成员函数
要调用Tag的get_size(),需要用boost::phoenix::bind包装成员函数指针,并将占位符_1作为调用该函数的对象参数传入:
_a = boost::phoenix::bind(&Tag::get_size, _1)
这里&Tag::get_size是成员函数指针,_1对应tag_parser解析出的Tag对象实例。
步骤2:修正解析器的完整实现
除了成员函数绑定,还要修正repeat中的逻辑问题——直接修改局部变量_a会导致循环计数异常,建议新增局部变量存储偏移位数,同时调整位运算逻辑让代码更清晰。
修正后的完整代码:
#include <boost/spirit/include/qi.hpp> #include <boost/spirit/include/phoenix.hpp> #include <cstdint> namespace qi = boost::spirit::qi; namespace phoenix = boost::phoenix; class Tag { public: Tag() = default; // Needed by qi Tag(std::uint8_t _raw_tag) : m_raw_tag(_raw_tag) {} std::uint8_t get_size() const { return m_raw_tag & 0b111; } // 加const保证绑定兼容性 std::uint8_t get_type() const { return m_raw_tag & 0b1000; } private: std::uint8_t m_raw_tag; }; template<typename _Iterator> struct Read_frame : qi::grammar<_Iterator, std::uint32_t(), qi::locals<std::uint8_t, std::uint8_t>> { Read_frame() : Read_frame::base_type(data_parser) { using qi::byte_; using qi::omit; using qi::repeat; using qi::_val; using qi::_a; using qi::_b; // 新增局部变量存储偏移计数 using phoenix::bind; tag_parser %= byte_; data_parser = omit[tag_parser[ _a = bind(&Tag::get_size, _1), _b = _a ]] >> qi::eps[_val = 0] >> repeat(_a)[ byte_[ _val = (_val << 8) | _1, --_b ] ]; } qi::rule<_Iterator, std::uint32_t(), qi::locals<std::uint8_t, std::uint8_t>> data_parser; qi::rule<_Iterator, Tag()> tag_parser; };
关键修正点说明
- const成员函数:将
get_size()改为const成员函数,因为Phoenix绑定非const成员函数时,要求对象必须是可修改的,而解析出的Tag对象通常是右值,const修饰更安全且兼容性更好。 - 避免修改循环计数:新增局部变量
_b存储偏移位数,repeat(_a)依赖_a的初始值,循环中修改_a会导致迭代次数混乱。改用左移操作拼接字节,逻辑更直观:每次读取字节后,将结果左移8位再与当前字节做或运算,等价于高位到低位依次存入。 - 替换
%=为=:data_parser包含手动语义动作,使用%=会触发自动属性传播,容易和手动逻辑冲突,直接用=更稳妥。 - Phoenix占位符本质:Phoenix占位符不是普通变量,而是表达式模板中的参数位置标记,所有对它们的操作都会被转化为Phoenix表达式,在解析阶段才会求值,因此必须用Phoenix提供的工具(如
bind)来包装成员函数调用。
内容的提问来源于stack exchange,提问作者PlayerK

