Search code examples
c++boostboost-spirit-qi

Parsing a boost::variant with User Defined Datatype containing std::string using Spirit Qi


I'm trying to create a GPIB parser using Spirit.Qi. Sometimes the response can be either an error or a normal response. This seemed like a good use case for the alternative parser which yields a boost::variant; however, if one of the variant types contains a string the code fails to compile. Here is a simplified version that reproduces the error.

struct C1 {
   std::string h{""};
   int i{0};
};
BOOST_FUSION_ADAPT_STRUCT(C1, (std::string, h)(int, i))

struct C2 {
   std::string h{""};
   std::string c{};
};
BOOST_FUSION_ADAPT_STRUCT(C2, (std::string, h)(std::string, c))

using VariantType = boost::variant<C1, C2>;

int main() {
   std::string s2{"C2:Zoo3"};
   VariantType v1;
   if(qi::parse(s1.begin(), s1.end(), 
                (qi::string("C1") >> ":" >> qi::int_) | 
                (qi::string("C2") >> ":" >> *qi::char_), 
                v1)) {
      if(boost::get<C1>(&v1)) {
         auto a1 = boost::get<C1>(v1);
         std::cout << "Parsing Succeeded, a1 = " << a1.h << ":" 
                   << a1.i << std::endl;
      }
      else {
         auto a2 = boost::get<C2>(v1);
         std::cout << "Parsing Succeeded, a2 = " << a2.h << ":" 
                   << a2.c << std::endl;
      }
   }
   else {
      std::cout << "Parsing Failed" << std::endl;
   }
   return 0;
}

I've experimented with various parsers other than *qi::char_ (eg. qi::string) to no avail. If I change C2::c to a char it works ok for 1 char. The best workaround I have found so far is to change C2::c to a std::vector<char>, this works fine but isn't ideal. I also tried telling Qi that std::string is a container like here. But qi knows what a std::string is so I'm pretty sure it ignores my customization. I think this is all because std::string isn't a POD and wasn't supported in unions until the rules were relaxed, but it works with boost::variant when it isn't in a struct, and std::vector works. Any ideas/workarounds would be appreciated.

*Note: I didn't post the compiler errors as they are long and mangled and I figured this was a known issue of std::string and variants. If they would be helpful let me know and I'll post them.


Solution

  • There's nothing wrong with the code, as far as I can tell.

    I've tested on c++1{1,4,y} and boost 1.{57,58}.0, using gcc {4.9,5.x} and clang++ 3.5.

    I suspect you may have an awkward Boost verion. Try using qi::as_string[*qi::char_] there. ¹

    Live On Coliru

    #include <iostream>
    #include <boost/fusion/adapted/struct.hpp>
    #include <boost/spirit/include/qi.hpp>
    
    struct C1 {
       std::string h{""};
       int i{0};
    
       friend std::ostream& operator<<(std::ostream& os, C1 const& c1) {
           return os << "C1 {h:'" << c1.h << "', i:'" << c1.i << "'}";
       }
    };
    
    struct C2 {
       std::string h{""};
       std::string c{};
    
       friend std::ostream& operator<<(std::ostream& os, C2 const& c2) {
           return os << "C2 {h:'" << c2.h << "', c:'" << c2.c << "'}";
       }
    };
    
    BOOST_FUSION_ADAPT_STRUCT(C1, (std::string, h)(int, i))
    BOOST_FUSION_ADAPT_STRUCT(C2, (std::string, h)(std::string, c))
    
    using VariantType = boost::variant<C1, C2>;
    namespace qi = boost::spirit::qi;
    
    int main() {
        VariantType value;
        for(std::string s1 : {
                "C2:Zoo3",
                "C1:1234"
                })
        {
            if(qi::parse(s1.begin(), s1.end(), 
                        (qi::string("C1") >> ":" >> qi::int_) | 
                        (qi::string("C2") >> ":" >> *qi::char_), 
                        value)) 
                std::cout << "Parsing Succeeded: " << value << "\n";
            else             
                std::cout << "Parsing Failed" << std::endl;
        }
    }
    

    Prints

    Parsing Succeeded: C2 {h:'C2', c:'Zoo3'}
    Parsing Succeeded: C1 {h:'C1', i:'1234'}
    

    ¹ I don't recommend qi::attr_cast<> as I remember this having had an awkward bug in awkwardly old boost versions.