c++ - 单成员结构的灵气属性传播问题

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

我遇到了 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

最佳答案

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

通常的解决方法是使暴露属性端变得不那么琐碎:

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

但是,这里是行不通的,因为你的 (a > XXX > b)子表达式导致另一个 vector1<decltype(member_type)>而这一次,没有多少智能括号或 eps -ing 会拯救你。[1]

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


1。 #define KEEP_STRING_WORKAROUND

查看Live On Coliru

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

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

如果identifier,这实际上只是推迟了使用融合自适应的魔法属性转换。 ,从而打破咒语:

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

刚刚好。我认为这可能是侵入性最小的解决方法


2。 #define DUMMY_WORKAROUND

查看Live On Coliru

因此你可以通过...使 identifier 解除魔法struct not fusion 适用于单元素融合序列。是的。这涉及添加虚拟字段的 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,这可能会更直观。

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

总结

我认为对于您的代码,有 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<>或辅助规则来强制转换子表达式的类型。

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

关于c++ - 单成员结构的灵气属性传播问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19823413/

相关文章:

c++ - 我如何转发声明 boost::spirit 规则?

c++ - 使用 Boost Spirit 2 解析字符串以在用户定义的结构中填充数据

c++ - 使用 visual studio 2015/2013 时缺少 opencv_world300.lib

c++ - 如何在 boost::spirit 语义操作中将函数对象的结果分配给本地

c++ - vsnprintf 与 GCC 有 %s 段错误

c++ - 灵气中如何将迭代器传给函数

c++ - 将解析器分配给自动变量

c++ - 为什么 boost spirit lex hung 而不是解析错误?

C++继承问题

C++ 以正确的方式在堆上声明静态连续多维数组