单成员结构体的灵气属性传递问题

9
我在Spirit Qi编译时遇到了问题,它抱怨value_type不是identifier的成员。由于某种原因,Qi的属性系统认为identifier是一个容器类型,并试图枚举它的值类型。

这与这个问题类似,但我认为原因是单个成员结构体,可能与这个错误有关。

#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可以正确编译(和执行)。

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

还请查看实时示例

1个回答

15

这是Spirit中一个相当臭名昭著的边界情况。问题是,Spirit中对于单元素Fusion序列的特殊处理会破坏一些抽象。

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

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

然而,在这里这不起作用,因为您的子表达式(a > XXX > b)会导致另一个vector1<decltype(member_type)>,这一次,无论多么聪明地使用括号或eps都无法解决问题。[1]

简而言之,我有三个解决方法:


1. #define KEEP_STRING_WORKAROUND

请在 Coliru 上查看实时演示

您可以简单地允许gr_identifier返回一个std::wstring[2]

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

这实际上只是推迟了使用Fusion自适应技术进行魔法属性转换的过程,如果使用identifier,因此打破了咒语:

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

只是起作用。我认为这可能是最不具侵入性的解决方法。


2. #define DUMMY_WORKAROUND

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

在 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 类型用于其他目的,则这可能更直观。

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

总结

对于您的代码,我认为有两种完全可行的解决方法(#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 更搭配。


1
但是对于只有一个字段且该字段是容器的类,应该怎么做呢?比如说“struct rvalue_list : tagged { std::deque <rvalue> rvalues_ };”。我能否像这样声明和定义构造函数:“template <typename Iterator> rvalue_list :: rvalue_list(Iterator const _first,Iterator const _last):rvalues_(_first,_last){; }”? - Tomilov Anatoliy
@Dukales 不是吗?您可以使构造函数接受公开的属性。无论如何,这是一种直接的方法:http://coliru.stacked-crooked.com/a/d81411f836f7f97c - 另外一种方法是使用自定义traits来分配到您的单字段类中,这样就有效地成为一个容器:http://coliru.stacked-crooked.com/a/e455a29ade08042b - sehe
1
看起来你已经回答了我几乎所有与boost相关的问题(如果我没记错的话,至少是spirit)。来自2018年的问候。 - Superlokkus
反过来,即NO_ADAPT_WORKAROUND的karma方式会是什么样子?我无法通过boost::spirit::karma::as弄清楚,我猜想这是因为我需要向std::vector<U/std::string>添加T/identifier构造函数,对吗?我的当前解决方法是使用boost::spirit::karma::attr_cast和一个自定义的boost::spirit::traits::transform_attribute<T, std::vector<u>>{static std::vector<U> pre (const &T)函数。 - Superlokkus
1
@Superlokkus 我觉得没有具体的代码很难回答。请注意,我几乎不使用Karma。例如,请参见https://dev59.com/oY_ea4cB1Zd3GeqPKB0f#32596517或https://stackoverflow.com/questions/46838958/boost-karma-generator-for-composition-of-classes/46841145#46841145(两种情况下都有答案),或者只需搜索[Lounge<C++>](https://chat.stackoverflow.com/rooms/10/loungec)。 - sehe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接