c++ - 在 Boost.Spirit 中,为什么 vector 需要融合包装器(包装在结构中),而不是变体?

标签 c++ boost-spirit boost-spirit-qi

我想了解在使用 Boost.Spirit 封装 struct 时需要 BOOST_FUSION_ADAPT_STRUCT 的确切场景。

下面是两个例子。一个例子是一个单成员 struct,(仅)有一个 variant 数据成员。此版本不需要将结构包装在 Fusion 容器中的 BOOST_FUSION_ADAPT_STRUCT 宏。构造函数足以让 Spirit 根据传入的 rhs 实例化/填充属性。

(请参阅代码中的注释以了解我认为由于属性折叠规则而由 Boost.Spirit 为规则定义的右侧生成的属性类型。)

第二个例子是一个单成员 struct,(仅)有一个 vector 数据成员。即使将构造函数定义为允许 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: vector

#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 Attribute Propagation is a heuristic machine.

Sadly, few things optimize for performance (X3 does a lot better). One of the key areas that is an exception is the incremental parsing into containers (even across multiple rules)¹.

This makes a lot of sense (since even e.g. building strings character-by-character would be extremely slow...). But it does lead to surprises (eg. boost::spirit::qi duplicate parsing on the output, Understanding Boost.spirit's string parser)

¹ (actually also non-containers, but I digress. I don't think it comes into to play without semantic actions)

一些不必要的体操

您实际上可以稍微更改属性传播触发的时间,并且不进行自适应,但我建议不要这样做:自适应更加一致和 self 描述:

Live On Coliru

#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 failed\n";
    }

    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++ - 在 Boost.Spirit 中,为什么 vector 需要融合包装器(包装在结构中),而不是变体?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47541757/

相关文章:

c++ - 为什么 std::function 实例有默认构造函数?

c++ - 将 Boost Spirit 解析器从 boost::variant 转换为 std::variant

c++ - 用精神解析成类?

c++ - Boost.Spirit 语法。属性和 _val 问题

c++ - 在 Boost.Spirit 中解析开头之前的短语结尾

c++ - 我可以提供对 OpenCV 矩阵的只读访问吗?

python - Python 中是否有 static_cast 关键字或标准方法?

c++ - 将文件路径字符串传递给 Boost.Spirit 中的语义操作

c++ - 如何从 Boost Spirit 中的函数中抛出 expectation_failure?

c++ - Qt OpenCV 应用程序无法在 Raspberry Pi 显示屏上运行