Boost.Qi 规则是否总是在使用它们的表达式中通过引用保存?

Are Boost.Qi rules always held by reference inside expressions that use them?

假设我有一个简单类型 foo:

struct foo { void bar(int) { // do something } };

我想使用 Boost.Qi 从字符串中解析整数字段,并使用结果值调用 foo::bar()。这可以通过语义操作来完成,如下所示:

std::string str;
bar b1, b2;
boost::spirit::phrase_parse(str.begin(), str.end(),
    boost::spirit::int_[boost::bind(&foo::bar, &b1, _1)] >>
    boost::spirit::int_[boost::bind(&foo::bar, &b2, _1)]);

这有很多重复,所以我可以像这样删除一些样板文件:

template <typename Iterator>
boost::spirit::qi::rule<Iterator, int(), boost::spirit::qi::space> parse_bar(bar *b)
{
    return boost::spirit::int_[boost::bind(&foo::bar, _1)];
}

std::string str;
bar b1, b2;
boost::spirit::phrase_parse(str.begin(), str.end(),
    parse_bar(&b1) >> parse_bar(b2));

这看起来更好而且有效。但是,有时我想保存解析器表达式供以后使用(例如,如果我想按值捕获它以便在 lambda 函数中延迟使用)。在原始示例中,我可以执行以下操作:

std::string str;
bar b1, b2;
auto parser = boost::proto::deep_copy(
    boost::spirit::int_[boost::bind(&foo::bar, &b1, _1)] >>
    boost::spirit::int_[boost::bind(&foo::bar, &b2, _1)]);
auto lambda = [parser]() { boost::spirit::phrase_parse(str.begin(), str.end(), parser); };

// some time later
lambda();

这也有效(尽管需要调用 boost::proto::deep_copy() 以确保 AST 中的 none 内部节点通过引用保存)。我的第一印象是我可以应用相同的 rule 重构来将上面的代码简化为:

std::string str;
bar b1, b2;
auto parser = boost::proto::deep_copy(parse_bar(&b1) >> parse_bar(&b2));
auto lambda = [parser]() { boost::spirit::phrase_parse(str.begin(), str.end(), parser); };

// some time later
lambda();

但是,这会导致稍后调用 lambda() 时出现问题。根据我的调试,parse_bar 返回的 rule 对象似乎总是通过引用 保存在表达式 中,即使在调用 [=23= 之后也是如此].由于 rule 对象在包含对 deep_copy() 的调用的行中是右值,因此在稍后对 phrase_parse().

的调用中对它们的引用无效

这似乎表明 rule 对象始终是左值,其生命周期至少与引用它们的表达式的生命周期相匹配。这是真的?我想我可能误解了库的一个关键概念,并且正在尝试这样做 "wrong way." 在我的应用程序中,我没有正式的语法;我正在寻找一种简单、紧凑的方法来定义大量内联的类似解析器表达式,并使用调用绑定成员函数的语义操作,如上所示。

简单回答:是的。

<!-- reads the rest of your question -->

您的代码示例中有很多不准确之处。

第一个片段

  1. bar b1, b2; // probably meant foo?
  2. boost::spirit::phrase_parse不存在,boost::spirit::int_
  3. 也不存在
  4. boost::bind 不能作为语义动作
  5. phrase_parse需要船长,你提供none

Fixed:

    std::string str = "123 234";
    qi::phrase_parse(str.begin(), str.end(),
            qi::int_[boost::bind(&foo::bar, &b1, _1)] >>
            qi::int_[boost::bind(&foo::bar, &b2, _1)],
            qi::space);

第二个片段

在随后的示例中,您有更多 barfoo 的混淆,您将 qi::space 作为类型参数传递,bind 无法绑定到 b 等等等等,不重复上面的内容,跳过明显的错误

  1. 存在模板函数需要作为parse_bar<std::string::const_iterator>使用的根本问题。我会说它不再看起来 "nice".
  2. 规则声明 int() 意味着您公开了一个属性,但它从未被分配或使用过

Fixed:

template <typename Iterator>
static qi::rule<Iterator, qi::space_type> parse_bar(foo *b) {
    return qi::int_ [boost::bind(&foo::bar, b, _1)];
}

int main() {
    foo b1, b2;
    using It = std::string::const_iterator;

    std::string const str = "234 345";
    qi::phrase_parse(str.begin(), str.end(), parse_bar<It>(&b1) >> parse_bar<It>(&b2), qi::space);

第三个片段

大部分是相同的问题,此外,str没有被捕获。

第四个片段

是的。看我回答的第一行

解决方案

跳出框框思考。您需要 "cute" 语法来将解析器绑定到带外属性。

备注

  • 这就像回到 Spirit V1(经典)设计。这不是图书馆的精神 [原文如此],可以这么说
  • 如果你只想要那个,使用继承的属性?

    std::string const str = "111 222";
    
    // use phoenix action 
    auto foo_bar = std::mem_fn(&foo::bar);
    px::function<decltype(foo_bar)> foo_bar_(foo_bar);
    
    // with inherited attributes
    qi::rule<It, void(foo*)> _foo = qi::int_ [ foo_bar_(qi::_r1, qi::_1) ];
    
    auto lambda = [_foo,&b1,&b2,str] { qi::phrase_parse(str.begin(), str.end(), _foo(&b1) >> _foo(&b2), qi::space); };
    
    // some time later
    lambda();
    
    std::cout << b1 << ", " << b2 << "\n";
    
  • 如果您想要 'magical' 支持您的类型,例如 foo 并且逻辑上类似于 assignment/conversion,请使用特征:http://www.boost.org/doc/libs/1_62_0/libs/spirit/doc/html/spirit/advanced/customize.html

现场演示

我总是喜欢添加一个现场演示,所以在这里:

Live On Wandbox

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/bind.hpp>
#include <iostream>

namespace qi = boost::spirit::qi;
namespace px = boost::phoenix;

struct foo { 
    int value = 42;
    void bar(int i) { value = i; } 
    friend std::ostream& operator<<(std::ostream& os, foo const& f) { return os << "{" << f.value << "}"; }
}; 

template <typename Iterator>
static qi::rule<Iterator, qi::space_type> parse_bar(foo *b) {
    return qi::int_ [boost::bind(&foo::bar, b, _1)];
}

int main() {
    foo b1, b2;
    using It = std::string::const_iterator;

    // snippet 1
    {
        std::string str = "123 234";
        qi::phrase_parse(str.begin(), str.end(),
                qi::int_[boost::bind(&foo::bar, &b1, _1)] >>
                qi::int_[boost::bind(&foo::bar, &b2, _1)],
                qi::space);

        std::cout << b1 << ", " << b2 << "\n";
    }

    // snippet 2
    {
        std::string const str = "234 345";
        qi::phrase_parse(str.begin(), str.end(), parse_bar<It>(&b1) >> parse_bar<It>(&b2), qi::space);

        std::cout << b1 << ", " << b2 << "\n";
    }

    // snippet 3
    {
        std::string const str = "345 456";

        auto parser = boost::proto::deep_copy(
                qi::int_[boost::bind(&foo::bar, &b1, _1)] >>
                qi::int_[boost::bind(&foo::bar, &b2, _1)]);

        auto lambda = [parser,str]() { qi::phrase_parse(str.begin(), str.end(), parser, qi::space); };

        // some time later
        lambda();

        std::cout << b1 << ", " << b2 << "\n";
    }

    // snippet 4
    {
        std::string const str = "456 567";
        auto parser = boost::proto::deep_copy(parse_bar<It>(&b1) >> parse_bar<It>(&b2));
        auto lambda = [parser=qi::copy(parser), str]() { qi::phrase_parse(str.begin(), str.end(), parser, qi::space); };

        // some time later
        //lambda(); 
        //// no workey
    }

    // SOLUTIONS
    {
        std::string const str = "111 222";

        // use phoenix action 
        auto foo_bar = std::mem_fn(&foo::bar);
        px::function<decltype(foo_bar)> foo_bar_(foo_bar);

        // with inherited attributes
        qi::rule<It, void(foo*)> _foo = qi::int_ [ foo_bar_(qi::_r1, qi::_1) ];

        auto lambda = [_foo,&b1,&b2,str] { qi::phrase_parse(str.begin(), str.end(), _foo(&b1) >> _foo(&b2), qi::space); };

        // some time later
        lambda();

        std::cout << b1 << ", " << b2 << "\n";
    }
}