c++boost-spirit-qiboost-fusion

Embedding a boost-spirit grammar parsing into a struct into another grammar gives compilation errors


I'm using boost spirit to parse some text. For this I have two grammars. The first one parses a string into a struct, the second one, takes a grammar as template argument and uses it to parse a sequence of data. The second parser should be flexible enough to also handle other grammar return types. Since the original parser is too large to act as a minimal example, I have reduced the code as much as I can, leaving me with something that would not parse anything, but still results in the same compilation errors: (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,
    (float, 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, Result(), 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, std::vector<Element>(), 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, ElementParser<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, ElementParser<Iterator, Struct1>> struct1Parser;
  
// error: no matching function for call to 'Struct2::Struct2(const std::vector<Struct2, std::allocator<Struct2> >&)'
  SP<Iterator, ElementParser<Iterator, Struct2>> struct2Parser;
}

As long, as I am using simple types or vectors as return types of the ElementParser, everything is working fine, but as soon as I'm parsing into a struct (which in itself is working fine), the sequence parser SP seems to try some stange assignments. Why do the struct versions result in compilation errors?


Solution

  • Here is an even shorter example, demonstrating the same problem (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, std::vector<int>(), 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, std::vector<std::tuple<int, float>>(), ascii::space_type> vecT_dst = *vecT_src;
    }
    

    I think, the problem is, that vectors and tuples are handles quite similarly in the underlying boost::fusion library, so when it comes to flattening the vector, boost::fusion overshoots the goal and assignment fails. (Possibly by some kind of SFINAE mechanism.) Now, that flattening the vector does not work, the right-hand-side tuple parser's synthesized attribute is of type vector<vector<tuple<int, float>>>, as opposed to the expected vector<tuple<int, float>>.

    Knowing this, the (not very pretty) solution I've found (for the original example) is to manually create assignment function overloads for both expected forms:

      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());
      }
    

    and call these in a semantic action via a boost::phoenix function:

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

    Here is the whole working example (compiler explorer):

    #include <boost/fusion/adapted/std_tuple.hpp>
    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/spirit/include/qi.hpp>
    
    #include <vector>
    #include <tuple>
    
    namespace ascii = boost::spirit::ascii;
    namespace qi = boost::spirit::qi;
    namespace ql = qi::labels;
    namespace ph = boost::phoenix;
    
    
    struct Struct1
    {
      float f;
    };
    BOOST_FUSION_ADAPT_STRUCT(
        Struct1,
        (float, f))
    
    struct Struct2
    {
      float f;
      int i;
    };
    BOOST_FUSION_ADAPT_STRUCT(
        Struct2,
        (float, 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, Result(), ascii::space_type> element;
    };
    
    
    template<typename Iterator>
    class Struct2Tuple : public qi::grammar<Iterator, std::tuple<float, int>(), ascii::space_type>
    {
    public:
      using ValueType = std::tuple<float, int>;
      
      Struct2Tuple() : Struct2Tuple::base_type(tupleElement)
      {
        ph::function convert = [](auto const& s,
                                  auto& t)
        {
          t = std::make_tuple(s.f, s.i);
        };
        
        tupleElement = structElement[convert(ql::_1, qi::_val)];
      }
      
    private:
      qi::rule<Iterator, ValueType(), ascii::space_type> tupleElement;
      ElementParser<Iterator, Struct2> structElement;
    };
    
    
    template<typename Iterator,
             typename ElementParser,
             typename Element = typename ElementParser::ValueType>
    class SP : public qi::grammar<Iterator, std::vector<Element>(), ascii::space_type>
    {
    private:
      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());
      }
      
    public:
      SP()
        : SP::base_type(sequence)
      {
        ph::function append = [](auto& into,
                                 auto const& a1)
        {
          flattenAndAppend(into, a1);
        };
        
        sequence = (simpleVector % ',')[append(qi::_val, ql::_1)];
        simpleVector = qi::repeat(1)[simple];
      }
      
    private:
      using Rule = qi::rule<Iterator, std::vector<Element>(), 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, ElementParser<Iterator, std::vector<float>>> vectorParser;   // OK
      
      SP<Iterator, Struct2Tuple<Iterator>> struct2tupleParser;                  // OK.
      
      SP<Iterator, ElementParser<Iterator, std::tuple<float, float>>> tupleParser;   // now OK
      SP<Iterator, ElementParser<Iterator, Struct1>> struct1Parser;                  // now OK
      SP<Iterator, ElementParser<Iterator, Struct2>> struct2Parser;                  // now OK
    }