滥用 boost::phoenix::static_cast_ 来获取占位符后面的对象

问题描述 投票:0回答:1

这是我的问题。我尝试使用 boost::spirit::qi 并尝试使用“_1”和“_a”等占位符。我想访问 boost::qi/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() { return m_raw_tag & 0b111; }
  std::uint8_t get_type() { return m_raw_tag & 0b1000; }

private:
  std::uint8_t m_raw_tag;
};

我必须解析以标签字节开头的帧,该标签字节提供有关我接下来要阅读的内容的信息。为此,我编写了一个名为 Tag 的小帮助器类,它揭示了这些信息,例如标签的类型或接下来的数据的大小。我总是将数据存储在

std::uint32_t
中,但数据的大小可能是 3 个字节,而不是像 1、2 或 4 这样的预定义值,在这种情况下我可以分别使用
qi::byte
qi::big_word
qi::big_qword
(假设采用大字节序)。因此,我正在考虑逐字节读取数据并在输出中对它们进行位移
std::uint32_t
。 这将在伪 cpp 代码中给出这样的解析器:

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]

这就是我的问题所在。我不知道如何在语义操作中访问标签的方法。因此我考虑使用

boost::phoenix::static_cast_<Tag*>(&_1)->get_size()
或类似的东西,但它不起作用。
这是我第一次将整个
boost::spirit
boost::phoenix
一起使用,说实话,我认为我并不真正理解
boost
中的占位符如何工作,也不理解
boost::phoenix::static_cast_
的原理。这就是为什么我在这里温柔地请求你的帮助:)。如果您需要更多详细信息,我很乐意为您提供

提前致谢,

充满活力的新手

c++ boost boost-spirit-qi boost-phoenix
1个回答
0
投票

语义动作是懒惰的凤凰演员。也就是说,它们是“延迟函数”。您还可以将它们视为动态定义的组合函数。

“占位符背后的价值”取决于上下文。该上下文是运行时。 Phoenix 转换(“评估”)使用该上下文来检索占位符后面的实际对象在调用期间

最后一部分是要点:任何运行时效果都必须推迟到调用期间。这意味着您需要一个 Phoenix actor 来访问

get_size()
方法并延迟调用它。

笨拙?你打赌。整个语义动作eDSL 是有限的。幸运的是,有很多方法可以解决这个问题:

  • 您可以将

    phoenix::bind
    与指向成员函数的指针一起使用

  • 您可以使用许多预定义的惰性函数来进行构造或大多数 STL (

    #include <boost/phoenix/stl.hpp>
    ) 之类的事情。

    顺便说一句。

    phoenix::size
    不适用于您的类型,因为它不遵守 STL 约定(
    size_t T::size() const
    而不是
    get_size
    )。

  • 您可以将自己的参与者编写为多态函数对象,并对其进行调整

    • 使用 BOOST_FUNCTION_ADAPT_CALLABLE
    • phoenix::function<>

    事实上,我最喜欢的做法已经成为

    px::function f = [](auto& a, auto& b) { return a + b; };
    ,完全利用 C++17 CTAD

让我们演示所有或大部分这些。

步骤#1 限制行为

正如我的评论中提到的,我对给定的解析器的明显行为有点困惑,所以让我们首先使用

phoenix::bind
方法作为示例来确定它:

template <typename It> struct Read_frame : qi::grammar<It, uint32_t(), qi::locals<uint8_t>> {
    Read_frame() : Read_frame::base_type(data_parser) {
        using namespace qi::labels;

        tag_parser = qi::byte_;

        auto _size = px::bind(&Tag::get_size, _1);
        constexpr qi::_a_type _len;

        data_parser                                  //
            = tag_parser[(_len = _size, _val = 0)]   //
            >> qi::repeat(_len)[                     //
                   qi::byte_[_val += (_1 << --_len)] //
        ];
    }

    qi::rule<It, uint32_t(), qi::locals<uint8_t>> data_parser;
    qi::rule<It, Tag()> tag_parser;
};

注意其他几个简化/可读性技巧。现在有一些测试用例Live On Compiler Explorer

PASS [] -> none
PASS [0b00] -> optional(0)
PASS [0b01] -> none
PASS [0b01, 0b101010] -> optional(42)
PASS [0b10, 0b101010] -> none
PASS [0b10, 0b101010, 0b00] -> optional(84)
PASS [0b11, 0b101010, 0b00, 0b00] -> optional(168)
PASS [0b11111111] -> none
PASS [0b11111111, 0b01, 0b10, 0b11, 0b100, 0b101, 0b110, 0b111] -> optional(247)

步骤#2:简化

我不会改变 qi::local,而是简单地逐步改变:

    data_parser                                    //
        = tag_parser[(_len = _size, _val = 0)]     //
        >> qi::repeat(_len)[                       //
               qi::byte_[(_val <<= 1, _val += _1)] //
    ];

我们现在进行单元测试来验证行为是否相同:Live On Compiler Explorer

步骤 #3 其他绑定方法

正如所承诺的:

  • 使用

    phoenix::function
    和 C++17 lambda 优点:Live

     px::function get_size = [](Tag const& tag) { return tag.get_size(); };
    
     data_parser                                       //
         = tag_parser[(_len = get_size(_1), _val = 0)] //
         >> qi::repeat(_len)[                          //
                qi::byte_[(_val <<= 1, _val += _1)]    //
     ];
    

    请注意,延迟函数对象的本质是多态的,因此其工作原理是相同的:

     px::function get_size = [](auto& tag) { return tag.get_size(); };
    
  • 在没有 C++17 优点的情况下使用相同的内容:Live

    template <typename It> struct Read_frame : qi::grammar<It, uint32_t(), qi::locals<uint8_t>> {
        Read_frame() : Read_frame::base_type(data_parser) {
            using namespace qi::labels;
            constexpr qi::_a_type _len;
    
            tag_parser = qi::byte_;
    
            data_parser                                       //
                = tag_parser[(_len = get_size(_1), _val = 0)] //
                >> qi::repeat(_len)[                          //
                       qi::byte_[(_val <<= 1, _val += _1)]    //
            ];
        }
    
      private:
        struct get_size_f {
            auto operator()(Tag const& tag) const { return tag.get_size(); };
        };
        px::function<get_size_f> get_size{};
    
        qi::rule<It, uint32_t(), qi::locals<uint8_t>> data_parser;
        qi::rule<It, Tag()> tag_parser;
    };
    
  • 使用适应宏(

    BOOST_PHOENIX_ADAPT_CALLABLE
    ),Live

     namespace {
         struct get_size_f {
             auto operator()(Tag const& tag) const { return tag.get_size(); };
         };
    
         BOOST_PHOENIX_ADAPT_CALLABLE(get_size_, get_size_f, 1);
     } // namespace
    
     template <typename It> struct Read_frame : qi::grammar<It, uint32_t(), qi::locals<uint8_t>> {
         Read_frame() : Read_frame::base_type(data_parser) {
             using namespace qi::labels;
             constexpr qi::_a_type _len;
    
             tag_parser = qi::byte_;
    
             data_parser                                        //
                 = tag_parser[(_len = get_size_(_1), _val = 0)] //
                 >> qi::repeat(_len)[                           //
                        qi::byte_[(_val <<= 1, _val += _1)]     //
             ];
         }
    
       private:
         qi::rule<It, uint32_t(), qi::locals<uint8_t>> data_parser;
         qi::rule<It, Tag()> tag_parser;
     };
    

额外简化#2

仍然使用 Qi,我会注意到

Tag
中没有任何内容需要将其用作属性类型。事实上,我们只需要简单的位掩码,如果您确实想要的话,它可能是一个免费函数。因此,这个最少的代码可以实现相同的功能,而不会带来太多不必要的复杂性:

实时编译器资源管理器

#include <boost/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;

template <typename It> struct Read_frame : qi::grammar<It, uint32_t(), qi::locals<uint8_t>> {
    Read_frame() : Read_frame::base_type(start) {
        using namespace qi::labels;
        start                                          //
            = qi::byte_[(_val = 0, _a = _1 & 0b111)]   //
            >> qi::repeat(_a)[                         //
                   qi::byte_[(_val <<= 1, _val += _1)] //
        ];
    }

  private:
    qi::rule<It, uint32_t(), qi::locals<uint8_t>> start;
};

免费功能也同样简单:Live

start                                                         //
    = qi::byte_[(_val = 0, _a = px::bind(size_from_tag, _1))] //
    >> qi::repeat(_a)[                                        //
           qi::byte_[(_val <<= 1, _val += _1)]                //
];

奖励简化和现代化

在现实生活中,我当然会编写一个自定义解析器。你可以在 Spirit Qi 中做到这一点,但为了与时俱进,大大减少编译时间,并且总体上让我的生活更轻松,我会选择 Spirit X3:

实时编译器资源管理器

#include <boost/spirit/home/x3.hpp>

namespace Readers {
    namespace x3 = boost::spirit::x3;

    static constexpr uint8_t size_from_tag(uint8_t tag) { return tag & 0b111; }

    struct frame_parser : x3::parser<frame_parser> {
        using attribute_type = uint32_t;
        bool parse(auto& first, auto last, auto&& /*ctx*/, auto&& /*rcontext*/, auto& attr) const {
            if (first == last)
                return false;
            auto    save = first;
            uint8_t tag  = *first++;
            uint8_t len  = size_from_tag(tag);

            uint32_t val = 0;
            while (len && first != last) {
                --len;
                val <<= 1;
                val += static_cast<uint8_t>(*first++);
            }

            if (len == 0) {
                attr = val;
                return true;
            }
            first = save;
            return false;
        }
    } static frame;
} // namespace Readers

#include <fmt/ranges.h>
#include <fmt/std.h>
int main() {
    using Data = std::vector<uint8_t>;

    struct {
        Data                    input;
        std::optional<uint32_t> expected;
    } static const cases[]{
        {{}, {}}, // empty input, expect nothing in return
        {{0b0000}, 0},
        {{0b0001}, {}},                     // missing byte
        {{0b0001, 42}, 42},                 // 42
        {{0b0010, 42}, {}},                 // missing byte
        {{0b0010, 42, 0}, 2 * 42},          // 2*42
        {{0b0011, 42, 0, 0}, 4 * 42},       // 4*42
        {{0xff}, {}},                       // requires 7 bytes
        {{0xff, 1, 2, 3, 4, 5, 6, 7}, 247}, // like this
    };

    for (auto& [data, expected] : cases) {
        std::optional<uint32_t> actual;

        auto ok      = parse(begin(data), end(data), -Readers::frame, actual);
        auto pass    = (actual == expected);
        auto verdict = pass ? "PASS" : "FAIL";
        assert(ok); // optional parser should never fail, but we want to be sure
        if (pass)
            fmt::print("{} {::#04b} -> {}\n", verdict, data, actual);
        else
            fmt::print("{} {::#04b} -> {}\n\t *** expected: {}\n", verdict, data, actual, expected);
    }
}

注意,这只会使编译速度加快 10 倍,我怀疑编译器会更容易优化。确实是这个程序

constexpr uint32_t parse_frame(auto const& input) {
    uint8_t v;
    parse(begin(input), end(input), x3::expect[Readers::frame], v);
    return v;
}

int main() {
    return parse_frame(std::array<uint8_t, 3>{0b0010, 42, 0}); // 2*42
}

全程优化

main:
        mov     eax, 84
        ret

查看它Live On Compiler Explorer包括生成的汇编代码


¹ 通过手指浸渍证明

© www.soinside.com 2019 - 2024. All rights reserved.