在提升精神中使用语义动作来设置字段

Using semantic actions in boost spirit to set fields

假设您有一个使用 boost spirit 的解析器来设置除 id 字段之外的此字段。是否可以使用语义动作来生成和设置 id 字段?或者有没有更好的方法来实现这个。

struct employee
{

    std::string id;
    int age;
    std::string surname;
    std::string forename;
    double salary;
};
BOOST_FUSION_ADAPT_STRUCT(
    client::employee,
    (int, age)
    (std::string, id)
    (std::string, surname)
    (std::string, forename)
    (double, salary)
)

是的,这是可能的。

一个陷阱是语义动作的存在通常会抑制自动属性传播。由于您希望同时拥有两者,因此您需要使用 %= 而不是 = 来分配解析器表达式(请参阅 docs)。

或者,您可以动态生成一个值并使用您显示的改编。

概念验证:SA + 改编

这里我只是将 id 从改编中排除。另请注意,自 c++11 以来,您不需要重复类型:

BOOST_FUSION_ADAPT_STRUCT(
    client::employee, age, /*id, */ surname, forename, salary)

我更喜欢用一些 phoenix 函数助手来编写 SA:

    auto _id = px::function { std::mem_fn(&client::employee::id) };
    auto _gen = px::function { client::employee::generate_id };

    start %= skip(space) [
        age >> name >> name >> salary >> eps 
            [ _id(_val) = _gen() ]
    ];

Live On Coliru

#include <boost/fusion/adapted.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <iomanip>
#include <iomanip>
namespace qi = boost::spirit::qi;
namespace px = boost::phoenix;
namespace fus = boost::fusion;

namespace client {
    struct employee {
        std::string id;
        int         age;
        std::string surname;
        std::string forename;
        double      salary;

        static std::string generate_id() {
            static int _next{0};
            return "autoid#" + std::to_string(_next++);
        }
    };
    using fus::operator<<;
}

BOOST_FUSION_ADAPT_STRUCT(
    client::employee, age, /*id, */ surname, forename, salary)

template <typename It>
struct Parser : qi::grammar<It, client::employee()> {
    Parser() : Parser::base_type(start) {
        using namespace qi;
        name   = +graph;
        age    = uint_ >> eps(_val < 110 && _val > 16);
        salary = double_;

        auto _id = px::function { std::mem_fn(&client::employee::id) };
        auto _gen = px::function { client::employee::generate_id };

        start %= skip(space) [
            age >> name >> name >> salary >> eps 
                [ _id(_val) = _gen() ]
        ];

        BOOST_SPIRIT_DEBUG_NODES((start)(name)(age)(salary))
    }
  private:
    qi::rule<It, client::employee()> start;
    qi::rule<It, unsigned()>         age;
    qi::rule<It, std::string()>      name;
    qi::rule<It, double()>           salary;
};

static auto qview(auto f, auto l) {
    return std::quoted(
        std::string_view(std::addressof(*f), std::distance(f, l)));
}

int main() {
    Parser<std::string::const_iterator> p;
    std::cout << fus::tuple_delimiter(',');

    for (std::string const& input: {
            //age surname forename salary
            "55 Astley Rick 7232.88",
            "23 Black Rebecca 0.00",
            "77 Waters Roger 24815.07",
        })
    {
        auto f = begin(input), l = end(input);

        client::employee emp;
        if (parse(f, l, p, emp)) {
            std::cout << "Parsed: " << emp.id << " " << emp << "\n";
        } else {
            std::cout << "Parse failed\n";
        }

        if (f != l) {
            std::cout << "Remaining unput: " << qview(f,l) << "\n";
        }
    }
}

版画

Parsed: autoid#0 (55,Astley,Rick,7232.88)
Parsed: autoid#1 (23,Black,Rebecca,0)
Parsed: autoid#2 (77,Waters,Roger,24815.1)

备选方案:内联生成

你会保持完全改编:

BOOST_FUSION_ADAPT_STRUCT(
    client::employee, age, id,  surname, forename, salary)

并在正确的位置使用 qi::attr() 重新拼写规则:

    auto _gen = px::function { client::employee::generate_id };

    start %= skip(space) [
        age >> attr(_gen()) >> name >> name >> salary
    ];

Live On Coliru(省略未更改列表的其余部分)

打印(再次):

Parsed: autoid#0 (55,autoid#0,Astley,Rick,7232.88)
Parsed: autoid#1 (23,autoid#1,Black,Rebecca,0)
Parsed: autoid#2 (77,autoid#2,Waters,Roger,24815.1)

结论

回想起来,我认为替代方案更有吸引力。