Spirit Qi attribute propagation issue with single-member struct

后端 未结 1 664
栀梦
栀梦 2020-11-27 06:54

I have an compilation issue with Spirit Qi where it complains that value_type is not a member of identifier. For some reason, Qi\'s attribute system consid

相关标签:
1条回答
  • 2020-11-27 07:12

    This is a quite infamous edge-case in Spirit. The problem is, special-case handling of single-element Fusion Sequences in Spirit breaks some abstractions.

    The usual workaround is to adapt the exposed-attribute side to be less-trivial:

    rule<It, single_member_struct()> r = eps >> XXX; 
    // the `eps` is there to break the spell
    

    However, here that won't work, because your (a > XXX > b) subexpression results in another vector1<decltype(member_type)> and this time, no amount of smart parenthesizing or eps-ing will save you.[1]

    To cut the long story short, I have three workarounds:


    1. #define KEEP_STRING_WORKAROUND

    See it Live On Coliru

    In which you'll simply allow gr_identifier to return a std::wstring[2]:

    rule<It, std::string()> gr_identifier = 
        (alpha | '_') >> *(alnum | '_');
    

    This effectively just postpones the magic attribute transformation that uses the Fusion adaptation if identifier, and thereby breaks the spell:

    rule<It, problem(), qi::space_type> gr_problem = 
           gr_identifier
        >> gr_identifier
        >> ('(' > gr_identifier > ')')
        ;
    

    Just works. I think this is probably the least intrusive workaround


    2. #define DUMMY_WORKAROUND

    See it Live On Coliru

    Whereby you unjinx the magix by ... making the identifier struct not fusion adapted to a single-element fusion sequence. Yes. This involves the EvilHack™ of adding a dummy field. To minimize confusion, lets' make it qi::unused_type though:

    struct identifier
    {
        std::string    name;
        qi::unused_type dummy;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(
        identifier,
        (std::string,    name)
        (qi::unused_type, dummy)
    )
    

    And now:

    rule<It, identifier()> gr_identifier = 
        (alpha | '_') >> *(alnum | '_') >> attr(42);   // that's hacky
    

    Works


    3. #define NO_ADAPT_WORKAROUND

    See it Live On Coliru

    The final workaround might be the most obvious: don't adapt the struct as a fusion sequence in the first place, and profit:

    struct identifier
    {
        std::string name;
    
        identifier() = default;
    
        explicit identifier(std::string name) 
            : name(std::move(name))
        {}
    };
    

    Note that to allow attribute propagation, now you will need suitable conversion constructors to be present. Also, the default constructor is required for exposed attributes in Spirit.

    Now,

    rule<It, identifier()> gr_identifier = 
        as_string [ (alpha | '_') >> *(alnum | '_') ]; // cleaner... but no fusion
    

    works. This might be more intuitive if you didn't need Fusion of the type for other purposes.

    Note: this variant could well be the most efficient in compile-time

    Summary

    I think for your code, there are 2 perfectly viable workarounds (#1 and #3), and one less-than-stellar (the one with the dummy field), but I included it for documentary purposes.

    Full Code

    For future reference

    #define BOOST_SPIRIT_DEBUG
    #include <string>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/fusion/include/adapt_struct.hpp>
    
    namespace qi = boost::spirit::qi;
    
    //////////////////////////////////////////
    // Select workaround to demonstrate
    #define KEEP_STRING_WORKAROUND
    // #define DUMMY_WORKAROUND™
    // #define NO_ADAPT_WORKAROUND
    //////////////////////////////////////////
    
    #if defined(KEEP_STRING_WORKAROUND)
        struct identifier
        {
            std::string    name;
        };
    
        BOOST_FUSION_ADAPT_STRUCT(
            identifier,
            (std::string,    name)
        )
    #elif defined(DUMMY_WORKAROUND)
        struct identifier
        {
            std::string    name;
            qi::unused_type dummy;
        };
    
        BOOST_FUSION_ADAPT_STRUCT(
            identifier,
            (std::string,    name)
            (qi::unused_type, dummy)
        )
    #elif defined(NO_ADAPT_WORKAROUND)
        struct identifier
        {
            std::string name;
    
            identifier() = default;
    
            explicit identifier(std::string name) 
                : name(std::move(name))
            {}
        };
    #endif
    
    struct problem
    {
        identifier _1;
        identifier _2;
        identifier _3;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(
        problem,
        (identifier, _1)
        (identifier, _2)
        (identifier, _3)
    )
    
    //////////////////////////////////////////
    // For BOOST_SPIRIT_DEBUG only:
    static inline std::ostream& operator<<(std::ostream& os, identifier const& id) {
        return os << id.name;
    }
    //////////////////////////////////////////
    
    int main()
    {
        using namespace qi;
        typedef std::string::const_iterator It;
    #if defined(KEEP_STRING_WORKAROUND)
        rule<It, std::string()> gr_identifier = 
            (alpha | '_') >> *(alnum | '_');
    #elif defined(DUMMY_WORKAROUND)
        rule<It, identifier()> gr_identifier = 
            (alpha | '_') >> *(alnum | '_') >> attr(42);   // that's hacky
    #elif defined(NO_ADAPT_WORKAROUND)
        rule<It, identifier()> gr_identifier = 
            as_string [ (alpha | '_') >> *(alnum | '_') ]; // cleaner... but no fusion
    #endif
    
        rule<It, problem(), qi::space_type> gr_problem = 
               gr_identifier
            >> gr_identifier
            >> ('(' > gr_identifier > ')')
            ;
    
        std::string input = "foo goo(hoo)";
    
        BOOST_SPIRIT_DEBUG_NODES((gr_problem)(gr_identifier));
    
        It f(begin(input)), l(end(input));
        bool dummy = phrase_parse(f, l, gr_problem, qi::space);
    
        return dummy? 0 : 255;
    }
    

    [1] Believe me, I tried, even while inserting qi::unused_type "fake" attributes, and/or using attr_cast<> or helper rules to coerce the type of the subexpression.

    [2] For demo purposes, I used std::string because I believe it mixes better with BOOST_SPIRIT_DEBUG

    0 讨论(0)
提交回复
热议问题