Search code examples
c++boostboost-spiritboost-spirit-qiboost-spirit-x3

Type safety of Boost Qi/X3 parse


Consider this code using Boost Spirit X3 (conceptually same goes for Boost Spirit Qi):

  string command;
  string value;
  x3::parse(command.begin(), command.end(), "float:" >> x3::double_, value);

Why is this code not generating any error during compilation? Shouldn't "float:" >> x3::double_ parser have attribute of type double and therefore not accept std::string as 4th argument to parse?

BTW, I know I could do this:

string value;
auto assign = [](auto& ctx){value = _attr(ctx)};
x3::parse(command.begin(), command.end(), "float:" >> x3::double_[assign], value)

which would generate an error, but it is more complicated than necessary.

As a last resort: any there any well-known replacements for sscanf that would be type safe (possibly in boost)?


Solution

  • It's because automatic attribute propagation into a container is very flexible.

    And std::string is a container.

    Perhaps if we do the following experiment it makes a bit more sense:

    Live On Coliru

    #include <boost/spirit/home/x3.hpp>
    #include <iostream>
    namespace x3 = boost::spirit::x3;
    
    int main() {
        //std::string command = "f: hello world";
        std::string command = "f: 104 101 108 108 111 32 119 111 114 108 100";
        std::string value;
        x3::phrase_parse(
                command.begin(),
                command.end(),
                "f:" >> *x3::double_, x3::blank,
                value);
    
        std::cout << value << "\n";
    }
    

    As you might surmise, it prints:

    hello world
    

    For strings this seems surprising. It would be much less of a surprise if the target was std::vector<float> - but it would require precisely the same amount of conversions.

    It's fair for each element to enjoy propagation conversions. What if you were parsing Ast nodes like:

    struct Operation {
         Operator op;
         std::vector<Expression> operands;
    }
    

    You would hate if operands didn't implicitly convert into Expression from Unary, FunctionCall, StringLiteral etc.