【问题标题】:Spirit Qi attribute propagation issue with single-member struct单成员结构的灵气属性传播问题
【发布时间】:2013-11-18 08:36:54
【问题描述】:

我遇到了 Spirit Qi 的编译问题,它抱怨 value_type 不是 identifier 的成员。由于某种原因,Qi 的属性系统将标识符视为容器类型,并尝试枚举其值类型。

这与this question 中的问题类似,但是,我认为原因是单个成员结构,可能与此bug 有关。

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

using namespace boost::spirit::qi; 

struct identifier
{
    std::wstring name;
};

struct problem
{
    identifier _1;
    identifier _2;
    identifier _3;
};

BOOST_FUSION_ADAPT_STRUCT(
    identifier,
    (std::wstring, name)
)

BOOST_FUSION_ADAPT_STRUCT(
    problem,
    (identifier, _1)
    (identifier, _2)
    (identifier, _3)
)



int main(int argc, char* argv[])
{
    rule<std::wstring::const_iterator, identifier()> gr_identifier = eps >> raw[lexeme[(alpha | '_') >> *(alnum | '_')]];

    // Ok, compiles
    /*rule<std::wstring::const_iterator, problem()> gr_problem =       gr_identifier
                                                                  >> gr_identifier
                                                                  >> '('
                                                                  >>   gr_identifier
                                                                  >>   ')';*/
    // Fails
    rule<std::wstring::const_iterator, problem()> gr_problem =       gr_identifier
                                                                  >> gr_identifier
                                                                  >> '('
                                                                  >   gr_identifier
                                                                  >   ')';

    std::wstring input = L"foo goo(hoo)";
    /*bool dummy = phrase_parse(
            input.begin(), input.end(), 
            gr_problem,
            space);*/

    return EXIT_SUCCESS;
}

有趣的是,这只发生在使用期望解析器时(参见示例中的定义 2)。定义 1,仅使用序列解析器,可以正确编译(并执行)。

有人知道正确的解决方案吗?

也请查看live example

【问题讨论】:

    标签: c++ boost-spirit boost-spirit-qi boost-fusion


    【解决方案1】:

    这是 Spirit 中一个非常臭名昭著的边缘案例。问题是,Spirit 中单元素融合序列的特殊情况处理破坏了一些抽象。

    通常的解决方法是调整暴露属性的一面,使其不那么琐碎:

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

    但是,在这里这行不通,因为您的 (a &gt; XXX &gt; b) 子表达式会导致另一个 vector1&lt;decltype(member_type)&gt;,而这一次,再多的智能括号或 eps-ing 都无法挽救您。[1]

    长话短说,我有三个解决方法:


    1。 #define KEEP_STRING_WORKAROUND

    Live On Coliru

    您只需允许gr_identifier 返回std::wstring[2]

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

    这实际上只是推迟了使用 Fusion 适配 if identifiermagic 属性转换,从而打破了咒语:

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

    只是工作。我认为这可能是侵入性最小的解决方法


    2。 #define DUMMY_WORKAROUND

    Live On Coliru

    因此,您可以通过...使identifier 结构不融合以适应单元素融合序列。是的。这涉及到添加虚拟字段的EvilHack™。为了尽量减少混淆,让我们将其设为qi::unused_type

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

    现在:

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

    作品


    3。 #define NO_ADAPT_WORKAROUND

    Live On Coliru

    最终的解决方法可能是最明显的:首先不要将结构调整为融合序列,然后获利:

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

    请注意,为了允许属性传播,现在您需要合适的转换构造函数。此外,Spirit 中暴露的属性需要默认构造函数。

    现在,

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

    有效。如果您不需要 Fusion 类型的其他用途,这可能会更直观。

    注意:这个变体很可能是编译时最高效的

    总结

    我认为对于您的代码,有 2 种完全可行的解决方法(#1 和 #3),还有一种不太出色(带有虚拟字段的),但我将其包含在文档中。

    完整代码

    供日后参考

    #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] 相信我,我尝试过,即使在插入 qi::unused_type“假”属性和/或使用 attr_cast&lt;&gt; 或辅助规则来强制子表达式的类型时。 p>

    [2] 出于演示目的,我使用了std::string,因为我相信它与 BOOST_SPIRIT_DEBUG 混合效果更好

    【讨论】:

    • 但是单字段类怎么办,哪个字段是容器呢?说struct rvalue_list : tagged { std::deque&lt; rvalue &gt; rvalues_ };。我可以像template&lt; typename Iterator &gt; rvalue_list::rvalue_list(Iterator const _first, Iterator const _last) : rvalues_(_first, _last) { ; }一样声明和定义c-tor吗?
    • @Dukales 没有?你会让构造函数接受暴露的属性。无论如何,这是直接的方法:coliru.stacked-crooked.com/a/d81411f836f7f97c - 另一种方法是使用自定义 traits 分配给您的单字段类,这实际上是一个容器:coliru.stacked-crooked.com/a/e455a29ade08042b
    • 似乎你已经回答了我曾经和将会遇到的几乎所有与提升相关的问题(至少是 IIRC 精神)。 2018 年的问候。
    • NO_ADAPT_WORKAROUND 的反向即业力方式会是什么样子?我无法用 boost::spirit::karma::as 弄清楚,我猜是因为我必须向 std::vector 添加一个 T/identifier 构造函数,对吧?我当前的解决方法是 boost::spirit::karma::attr_cast 与自定义 boost::spirit::traits::transform_attribute>{static std::vector pre (const &T) 函数
    • @Superlokkus 我发现没有具体代码很难回答。请注意,我实际上不使用 Karma。参见例如stackoverflow.com/questions/32579406/…stackoverflow.com/questions/46838958/…(两种情况下的两个答案),或者直接搜索 Lounge<C++>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-04
    • 2011-07-25
    • 2011-03-04
    • 1970-01-01
    • 1970-01-01
    • 2023-03-15
    相关资源
    最近更新 更多