将解析到结构中的 boost-spirit 语法嵌入到另一个语法中会导致编译错误

问题描述

我正在使用 boost 精神来解析一些文本。为此,我有两个语法。第一个将字符串解析为结构,第二个将语法作为模板参数并使用它来解析数据序列。第二个解析器应该足够灵活以处理其他语法返回类型。由于原始解析器太大而无法作为最小示例,因此我尽可能地减少了代码,留下了一些不会解析任何内容的东西,但仍然导致相同的编译错误:(Code on Coliru )

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>

#include <vector>

namespace ascii = boost::spirit::ascii;
namespace qi = boost::spirit::qi;


struct Struct1
{
  float f;
};
BOOST_FUSION_ADAPT_STRUCT(
    Struct1,(float,f))

struct Struct2
{
  float f;
  int i;
};
BOOST_FUSION_ADAPT_STRUCT(
    Struct2,f)
    (int,i))


template<typename Iterator,typename Result>
class ElementParser : public qi::grammar<Iterator,Result(),ascii::space_type>
{
public:
  using ValueType = Result;
  
  ElementParser() : ElementParser::base_type(element) {}
  
private:
  qi::rule<Iterator,ascii::space_type> element;
};


template<typename Iterator,typename ElementParser,typename Element = typename ElementParser::ValueType>
class SP : public qi::grammar<Iterator,std::vector<Element>(),ascii::space_type>
{
public:
  SP()
    : SP::base_type(sequence)
  {
    sequence %= simpleVector % ',';
    // The simpleVector hack is really needed because of some other parsing
    // stuff,that is going on,but has been left out here.
    simpleVector %= qi::repeat(1)[simple];
  }
  
private:
  using Rule = qi::rule<Iterator,ascii::space_type>;
  Rule sequence;
  Rule simpleVector;
  ElementParser simple;
};


void sequencetest()
{
  using Iterator = std::string::const_iterator;
  
  SP<Iterator,qi::uint_parser<>,std::size_t> uintParser;                  // OK
  SP<Iterator,ElementParser<Iterator,float>> floatParser;                 // OK
  SP<Iterator,std::vector<float>>> vectorParser;   // OK
  
// error: invalid static_cast from type 'const std::vector<Struct1,std::allocator<Struct1> >' to type 'element_type' {aka 'float'}
  SP<Iterator,Struct1>> struct1Parser;
  
// error: no matching function for call to 'Struct2::Struct2(const std::vector<Struct2,std::allocator<Struct2> >&)'
  SP<Iterator,Struct2>> struct2Parser;
}

只要我使用简单类型或向量作为 ElementParser 的返回类型,一切正常,但是一旦我解析为一个结构(它本身工作正常),序列解析器 SP 似乎尝试了一些奇怪的分配。为什么struct版本会导致编译错误

解决方法

我认为您绕过了古老的单元素序列兼容性规则。特别是 Struct1 确实适合作为单元素序列。

但是,在您的代码中,我可以通过删除不必要的 repeat(1)[] 装置轻松使其工作:

Live On Coliru

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>

namespace ascii = boost::spirit::ascii;
namespace qi = boost::spirit::qi;

struct Struct1 { float f; };
struct Struct2 { float f; int i; };
BOOST_FUSION_ADAPT_STRUCT(Struct1,f)
BOOST_FUSION_ADAPT_STRUCT(Struct2,f,i)

template <typename Iterator,typename Result>
class ElementParser
    : public qi::grammar<Iterator,Result(),ascii::space_type> {
  public:
    using ValueType = Result;

    ElementParser() : ElementParser::base_type(element) {
    }

  private:
    qi::rule<Iterator,ascii::space_type> element;
};

template <typename Iterator,typename ElementParser,typename Element = typename ElementParser::ValueType>
class SequenceParser
    : public qi::grammar<Iterator,std::vector<Element>(),ascii::space_type> {
  public:
    SequenceParser() : SequenceParser::base_type(sequence) {
        sequence = simple % ',';
    }

  private:
    qi::rule<Iterator,ascii::space_type> sequence;
    ElementParser simple;
};

void sequenceTest() {
    using It = std::string::const_iterator;

    SequenceParser<It,qi::uint_parser<>,std::size_t> uintParser;  // OK
    SequenceParser<It,ElementParser<It,float>> floatParser; // OK
    SequenceParser<It,std::vector<float>>>
        vectorParser; // OK

    SequenceParser<It,Struct1>> struct1Parser;
    SequenceParser<It,Struct2>> struct2Parser;
}

int main() {
    sequenceTest();
}

奖励:JustParseIt 魔法函数

请注意,在某种程度上,您似乎正在重新启动库设计。看看qi::auto_

添加这里的想法:

例如通过专门化 trait 来制作逗号分隔的序列解析器:

template <typename... T>
struct create_parser<std::vector<T...>> : comma_separated_sequence {};

struct comma_separated_sequence {
    using type = decltype(qi::copy(qi::auto_ % ','));
    static type call() { return qi::copy(qi::auto_ % ','); }
};

现在您可以实现一个适用于 /the world/ 的 JustParseIt 函数:

bool JustParseIt(std::string_view input,auto& val) {
    return qi::phrase_parse(input.begin(),input.end(),qi::auto_,qi::space,val);
}

你会惊讶地看到它解析的内容:

Live On Compiler Explorer

#include <boost/fusion/include/adapted.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/spirit/include/qi.hpp>
#include <fmt/ranges.h>
#include <fmt/ostream.h>
namespace qi = boost::spirit::qi;

namespace MyLib {
    struct Struct1 { float f; };
    struct Struct2 { float f; int i; };
    using boost::fusion::operator<<;
}
BOOST_FUSION_ADAPT_STRUCT(MyLib::Struct1,f)
BOOST_FUSION_ADAPT_STRUCT(MyLib::Struct2,i)

namespace {
    struct comma_separated_sequence {
        using type = decltype(qi::copy(qi::auto_ % ','));
        static type call() { return qi::copy(qi::auto_ % ','); }
    };
}

namespace boost::spirit::traits {
    template <typename... T>
    struct create_parser<std::list<T...>> : comma_separated_sequence {};

    template <typename... T>
    struct create_parser<std::vector<T...>> : comma_separated_sequence {};
}

bool JustParseIt(std::string_view input,auto& val) {
#ifdef BOOST_SPIRIT_DEBUG
    using It      = decltype(input.begin());
    using Skipper = qi::space_type;
    using Attr    = std::decay_t<decltype(val)>;
    static qi::rule<It,Attr(),Skipper> parser = qi::auto_;
    BOOST_SPIRIT_DEBUG_NODE(parser);
    return qi::phrase_parse(input.begin(),parser,val);
#else
    return qi::phrase_parse(input.begin(),val);
#endif
}

int main() {
    using namespace MyLib;
    std::cerr << std::boolalpha; // make debug easier to read

    float f;
    JustParseIt("3.1415",f);

    uint64_t u;
    JustParseIt("00897823",u);

    Struct1 s1;
    JustParseIt("3.1415",s1);

    Struct2 s2;
    JustParseIt("3.1415 00897823",s2);

    std::list<float> floats;;
    JustParseIt("1.2,3.4",floats);

    std::list<Struct1> list1;
    JustParseIt("1.2",list1);
    JustParseIt("1.2,-inf,9e10,NaN",list1);

    std::vector<boost::variant<Struct2,bool> > variants;
    JustParseIt("true,9e10 123,NaN 234,false,false",variants);

    std::vector<Struct2> vec2;
    JustParseIt("9e10 123,NaN 234",vec2);

    // this is pushing it - for lack of structurual syntax
    std::vector<std::tuple<bool,Struct1,std::vector<Struct2>>> insane;
    JustParseIt("true 3.14 1e1 1,2e2 2,3e3 3,false +inf 4e4 4",insane);

    fmt::print("float f: {}\n"
            "uint64_t u: {}\n"
            "std::list<float> floats: {}\n"
            "std::list<Struct1> list1: {}\n"
            "std::vector<Struct2> vec2: {}\n"
            "Struct1 s1: {}\n"
            "Struct2 s2: {}\n"
            "std::vector<boost::variant<Struct2,bool> > variants: {}\n"
            "std::vector<std::tuple<bool,std::vector<Struct2>>> "
            "insane: {}\n",u,floats,list1,vec2,s1,s2,variants,insane);
}

印刷品

float f: 3.1415
uint64_t u: 897823
std::list<float> floats: {1.2,3.4}
std::list<Struct1> list1: {(1.2),(1.2),(-inf),(9e+10),(nan)}
std::vector<Struct2> vec2: {(9e+10 123),(nan 234)}
Struct1 s1: (3.1415)
Struct2 s2: (3.1415 897823)
std::vector<boost::variant<Struct2,bool> > variants: {1,(9e+10 123),(nan 234),0}
std::vector<std::tuple<bool,std::vector<Struct2>>> insane: {(true,(3.14),{(10 1),(200 2),(3000 3)}),(false,(inf),{(40000 4)})}

请注意,您可以 define BOOST_SPIRIT_DEBUG 将解析器调试到 stderr,例如

<parser>
  <try>true,NaN </try>
  <success></success>
  <attributes>[[true,[9e+10,123],[nan,234],false]]</attributes>
</parser>
,

这是一个更短的示例,演示了相同的问题 (compiler explorer):

#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/spirit/include/qi.hpp>

#include <vector>
#include <tuple>

namespace ascii = boost::spirit::ascii;
namespace qi = boost::spirit::qi;

void test()
{
  using Iterator = std::string::const_iterator;
  
  // OK
  qi::rule<Iterator,std::vector<int>(),ascii::space_type> vecI_src;
  qi::rule<Iterator,ascii::space_type> vecI_dst = *vecI_src;
  
  // error: no matching function for call to 'std::tuple<int,float>::tuple(const std::vector<std::tuple<int,float> >&)'
  qi::rule<Iterator,std::vector<std::tuple<int,float>>(),ascii::space_type> vecT_src;
  qi::rule<Iterator,ascii::space_type> vecT_dst = *vecT_src;
}

我认为,问题在于,向量和元组在底层 boost::fusion 库中的处理方式非常相似,因此在展平向量时,boost::fusion 超出了目标并且分配失败。 (可能通过某种 SFINAE 机制。)现在,扁平化向量不起作用,右侧 tuple 解析器的合成属性是 vector<vector<tuple<int,float>>> 类型,而不是预期的 {{ 1}}。

知道这一点后,我发现的(不是很漂亮)解决方案(对于原始示例)是为两种预期形式手动创建赋值函数重载:

vector<tuple<int,float>>

并通过 static void flattenAndAppend(std::vector<Element>& into,std::vector<std::vector<Element>> const& vector) { for(auto const& subvector: vector) { into.insert(into.end(),subvector.begin(),subvector.end()); } } static void flattenAndAppend(std::vector<Element>& into,std::vector<Element> const& vector) { into.insert(into.end(),vector.begin(),vector.end()); } 函数在语义操作中调用这些:

boost::phoenix

这是整个工作示例 (compiler explorer):

    ph::function append = [](auto& into,auto const& a1)
    {
      flattenAndAppend(into,a1);
    };
    
    sequence = (simpleVector % ',')[append(qi::_val,ql::_1)];

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...