使用Boost Spirit和Fusion以关联方式解析结构体

3
我正在尝试将一个键值字符串解析成结构体。有些键值可能不存在或以不同的顺序出现,因此我想使用boost::fusion来适配结构体,然后使用at_key<>指令解析到它中去。
#include <iostream>
#include <string>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/fusion/adapted.hpp>
#include <boost/fusion/sequence.hpp>

using namespace std;
namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
namespace phx = boost::phoenix;

using boost::fusion::at_key;

typedef string::const_iterator iter_type;

struct Couple {
    int a;
    int b;
    Couple() : a(0), b(0) {}
};

namespace keys {
    struct first;
    struct second;
}

BOOST_FUSION_ADAPT_ASSOC_STRUCT(
    Couple,
    (int, a, keys::first)
    (int, b, keys::second)
    )


struct G: qi::grammar< iter_type, Couple(), ascii::space_type >
{
    G() : G::base_type( start_rule ) {
        using qi::_val;
        using qi::_1;
        using qi::_2;

        start_rule =
                        ( "first" >> qi::int_
                                [ at_key<keys::first>(_val) = _1 ]
                        )
                    ^
                        ( "second" >> qi::int_
                                [ at_key<keys::second>(_val) = _1 ]
                        );
    }

    qi::rule< iter_type, Couple(), ascii::space_type > start_rule;
};

int main() {
    Couple couple;
    string example = "second 2 first 1";
    iter_type begin( example.begin() );
    iter_type end( example.end() );

    // test at_key -- compiles with no error
    at_key<keys::second>(couple) = 5;

    bool ok = qi::phrase_parse( begin, end, G(), ascii::space, couple );
    if ( ok )
        cout << couple.a << " " << couple.b << endl;
    else
        cout << "Parse failed" << endl;

    return 0;
}

问题在于代码无法编译(使用了Boost 1.50.0,g++ 4.5.0和MinGW),似乎是在at_key<>规则处出现了错误:

In file included from D:\projects\workspace\boost/boost/fusion/support/category_of.hpp:10:0,
                 from D:\projects\workspace\boost/boost/fusion/include/category_of.hpp:10,
                 from D:\projects\workspace\boost/boost/proto/fusion.hpp:20,
                 from D:\projects\workspace\boost/boost/proto/core.hpp:21,
                 from D:\projects\workspace\boost/boost/proto/proto.hpp:12,
                 from D:\projects\workspace\boost/boost/spirit/home/support/meta_compiler.hpp:19,
                 from D:\projects\workspace\boost/boost/spirit/home/qi/meta_compiler.hpp:14,
                 from D:\projects\workspace\boost/boost/spirit/home/qi/action/action.hpp:14,
                 from D:\projects\workspace\boost/boost/spirit/home/qi/action.hpp:14,
                 from D:\projects\workspace\boost/boost/spirit/home/qi.hpp:14,
                 from D:\projects\workspace\boost/boost/spirit/include/qi.hpp:16,
                 from ..\src\spirit02_test.cpp:11:
D:\projects\workspace\boost/boost/fusion/support/detail/category_of.hpp: In instantiation of 'boost::fusion::detail::fusion_category_of<const boost::phoenix::actor<boost::spirit::attribute<0> > >':
D:\projects\workspace\boost/boost/fusion/support/category_of.hpp:44:58:   instantiated from 'boost::fusion::extension::category_of_impl<boost::fusion::non_fusion_tag>::apply<const boost::phoenix::actor<boost::spirit::attribute<0> > >'
D:\projects\workspace\boost/boost/fusion/support/category_of.hpp:66:9:   instantiated from 'boost::fusion::traits::category_of<const boost::phoenix::actor<boost::spirit::attribute<0> > >'
D:\projects\workspace\boost/boost/fusion/support/category_of.hpp:73:9:   instantiated from 'boost::fusion::traits::is_associative<const boost::phoenix::actor<boost::spirit::attribute<0> > >'
D:\projects\workspace\boost/boost/mpl/if.hpp:67:11:   instantiated from 'boost::mpl::if_<boost::fusion::traits::is_associative<const boost::phoenix::actor<boost::spirit::attribute<0> > >, boost::fusion::result_of::key_of<mpl_::arg<1> >, boost::fusion::result_of::value_of<mpl_::arg<1> > >'
D:\projects\workspace\boost/boost/fusion/algorithm/query/find.hpp:45:9:   instantiated from 'boost::fusion::result_of::find<const boost::phoenix::actor<boost::spirit::attribute<0> >, keys::first>'
D:\projects\workspace\boost/boost/fusion/sequence/intrinsic/at_key.hpp:38:17:   instantiated from 'boost::fusion::extension::at_key_impl<boost::fusion::non_fusion_tag>::apply<const boost::phoenix::actor<boost::spirit::attribute<0> >, keys::first>'
D:\projects\workspace\boost/boost/fusion/sequence/intrinsic/at_key.hpp:71:9:   instantiated from 'boost::fusion::result_of::at_key<const boost::phoenix::actor<boost::spirit::attribute<0> >, keys::first>'
..\src\spirit02_test.cpp:54:35:   instantiated from here
D:\projects\workspace\boost/boost/fusion/support/detail/category_of.hpp:15:38: error: no type named 'category' in 'const struct boost::phoenix::actor<boost::spirit::attribute<0> >'
In file included from D:\projects\workspace\boost/boost/proto/args.hpp:21:0,
                 from D:\projects\workspace\boost/boost/proto/core.hpp:14,
                 from D:\projects\workspace\boost/boost/proto/proto.hpp:12,
                 from D:\projects\workspace\boost/boost/spirit/home/support/meta_compiler.hpp:19,
                 from D:\projects\workspace\boost/boost/spirit/home/qi/meta_compiler.hpp:14,
                 from D:\projects\workspace\boost/boost/spirit/home/qi/action/action.hpp:14,
                 from D:\projects\workspace\boost/boost/spirit/home/qi/action.hpp:14,
                 from D:\projects\workspace\boost/boost/spirit/home/qi.hpp:14,
                 from D:\projects\workspace\boost/boost/spirit/include/qi.hpp:16,
                 from ..\src\spirit02_test.cpp:11:
D:\projects\workspace\boost/boost/mpl/if.hpp: In instantiation of 'boost::mpl::if_<boost::fusion::traits::is_associative<const boost::phoenix::actor<boost::spirit::attribute<0> > >, boost::fusion::result_of::key_of<mpl_::arg<1> >, boost::fusion::result_of::value_of<mpl_::arg<1> > >':
D:\projects\workspace\boost/boost/fusion/algorithm/query/find.hpp:45:9:   instantiated from 'boost::fusion::result_of::find<const boost::phoenix::actor<boost::spirit::attribute<0> >, keys::first>'
D:\projects\workspace\boost/boost/fusion/sequence/intrinsic/at_key.hpp:38:17:   instantiated from 'boost::fusion::extension::at_key_impl<boost::fusion::non_fusion_tag>::apply<const boost::phoenix::actor<boost::spirit::attribute<0> >, keys::first>'
D:\projects\workspace\boost/boost/fusion/sequence/intrinsic/at_key.hpp:71:9:   instantiated from 'boost::fusion::result_of::at_key<const boost::phoenix::actor<boost::spirit::attribute<0> >, keys::first>'
..\src\spirit02_test.cpp:54:35:   instantiated from here
D:\projects\workspace\boost/boost/mpl/if.hpp:67:11: error: 'value' is not a member of 'boost::fusion::traits::is_associative<const boost::phoenix::actor<boost::spirit::attribute<0> > >'
D:\projects\workspace\boost/boost/mpl/if.hpp:70:41: error: 'value' is not a member of 'boost::fusion::traits::is_associative<const boost::phoenix::actor<boost::spirit::attribute<0> > >'
In file included from D:\projects\workspace\boost/boost/fusion/sequence/intrinsic.hpp:20:0,
                 from D:\projects\workspace\boost/boost/fusion/include/intrinsic.hpp:10,
                 from D:\projects\workspace\boost/boost/proto/fusion.hpp:22,
                 from D:\projects\workspace\boost/boost/proto/core.hpp:21,
                 from D:\projects\workspace\boost/boost/proto/proto.hpp:12,
                 from D:\projects\workspace\boost/boost/spirit/home/support/meta_compiler.hpp:19,
                 from D:\projects\workspace\boost/boost/spirit/home/qi/meta_compiler.hpp:14,
                 from D:\projects\workspace\boost/boost/spirit/home/qi/action/action.hpp:14,
                 from D:\projects\workspace\boost/boost/spirit/home/qi/action.hpp:14,
                 from D:\projects\workspace\boost/boost/spirit/home/qi.hpp:14,
                 from D:\projects\workspace\boost/boost/spirit/include/qi.hpp:16,
                 from ..\src\spirit02_test.cpp:11:
D:\projects\workspace\boost/boost/fusion/sequence/intrinsic/at_key.hpp: In instantiation of 'boost::fusion::extension::at_key_impl<boost::fusion::non_fusion_tag>::apply<const boost::phoenix::actor<boost::spirit::attribute<0> >, keys::first>':
D:\projects\workspace\boost/boost/fusion/sequence/intrinsic/at_key.hpp:71:9:   instantiated from 'boost::fusion::result_of::at_key<const boost::phoenix::actor<boost::spirit::attribute<0> >, keys::first>'
..\src\spirit02_test.cpp:54:35:   instantiated from here
D:\projects\workspace\boost/boost/fusion/sequence/intrinsic/at_key.hpp:38:17: error: no type named 'type' in 'struct boost::fusion::result_of::find<const boost::phoenix::actor<boost::spirit::attribute<0> >, keys::first>'
..\src\spirit02_test.cpp: In constructor 'G::G()':
..\src\spirit02_test.cpp:54:35: error: no matching function for call to 'at_key(const boost::spirit::_val_type&)'
In file included from D:\projects\workspace\boost/boost/fusion/sequence/intrinsic.hpp:20:0,
                 from D:\projects\workspace\boost/boost/fusion/include/intrinsic.hpp:10,
                 from D:\projects\workspace\boost/boost/proto/fusion.hpp:22,
                 from D:\projects\workspace\boost/boost/proto/core.hpp:21,
                 from D:\projects\workspace\boost/boost/proto/proto.hpp:12,
                 from D:\projects\workspace\boost/boost/spirit/home/support/meta_compiler.hpp:19,
                 from D:\projects\workspace\boost/boost/spirit/home/qi/meta_compiler.hpp:14,
                 from D:\projects\workspace\boost/boost/spirit/home/qi/action/action.hpp:14,
                 from D:\projects\workspace\boost/boost/spirit/home/qi/action.hpp:14,
                 from D:\projects\workspace\boost/boost/spirit/home/qi.hpp:14,
                 from D:\projects\workspace\boost/boost/spirit/include/qi.hpp:16,
                 from ..\src\spirit02_test.cpp:11:
D:\projects\workspace\boost/boost/fusion/sequence/intrinsic/at_key.hpp: At global scope:
D:\projects\workspace\boost/boost/fusion/sequence/intrinsic/at_key.hpp: In instantiation of 'boost::fusion::extension::at_key_impl<boost::fusion::non_fusion_tag>::apply<const boost::phoenix::actor<boost::spirit::attribute<0> >, keys::second>':
D:\projects\workspace\boost/boost/fusion/sequence/intrinsic/at_key.hpp:71:9:   instantiated from 'boost::fusion::result_of::at_key<const boost::phoenix::actor<boost::spirit::attribute<0> >, keys::second>'
..\src\spirit02_test.cpp:58:36:   instantiated from here
D:\projects\workspace\boost/boost/fusion/sequence/intrinsic/at_key.hpp:38:17: error: no type named 'type' in 'struct boost::fusion::result_of::find<const boost::phoenix::actor<boost::spirit::attribute<0> >, keys::second>'
..\src\spirit02_test.cpp: In constructor 'G::G()':
..\src\spirit02_test.cpp:58:36: error: no matching function for call to 'at_key(const boost::spirit::_val_type&)'

如果我使用一个更简单的规则(没有结合性),所有代码可以编译并且正常工作,但是这种解决方案有些脆弱:

    // A non-associative solution
    //start_rule %= ( ("first" >> qi::int_) ^ ("second" >> qi::int_) );

为什么我不能在语义动作中使用 at_key?有没有更好的方法将“关联”解析到非关联结构中?


到目前为止,我从未使用过at_key或BOOST_FUSION_ADAPT_ASSOC_STRUCT。但是:将其解析为std :: map<>是否是一种选择?也许您还可以更改语法以解析为boost :: optional,但这可能不可行或不被允许。 - duselbaer
@duselbaer std::map是可以接受的;使用本地变量并通过ref()访问它们可能会更快,但我仍然想避免额外的映射。 - Lyth
1个回答

7
你需要一个at_key的延迟版本。很遗憾,这个补丁目前仍未加入Spirit:
/*=============================================================================
    Copyright (c) 2005-2008 Hartmut Kaiser
    Copyright (c) 2005-2007 Joel de Guzman
    Copyright (c) 2011      Michael Caisse

    Distributed under the Boost Software License, Version 1.0. (See accompanying
    file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
==============================================================================*/
#ifndef PHOENIX_SEQUENCE_AT_KEY_HPP
#define PHOENIX_SEQUENCE_AT_KEY_HPP

#include <boost/fusion/include/at_key.hpp>
#include <boost/spirit/home/phoenix/core/actor.hpp>
#include <boost/spirit/home/phoenix/core/compose.hpp>
#include <boost/type_traits/remove_reference.hpp>

namespace boost { namespace phoenix
{
    template <typename Key>
    struct at_key_eval
    {
        template <typename Env, typename Tuple>
        struct result
        {
            typedef typename Tuple::template result<Env>::type tuple;
            typedef typename
                fusion::result_of::at_key<
                    typename remove_reference<tuple>::type, Key
                >::type
            type;
        };

        template <typename RT, typename Env, typename Tuple>
        static RT
        eval(Env const& env, Tuple const& t)
        {
            return fusion::at_key<Key>(t.eval(env));
        }
    };

    template <typename Key, typename Tuple>
    inline actor<typename as_composite<at_key_eval<Key>, Tuple>::type>
    at_key(Tuple const& tup)
    {
        return compose<at_key_eval<Key> >(tup);
    }

}}

#endif

如果您添加了它,您可以使用boost::phoenix::at_key:
using boost::phoenix::at_key;

事情将按预期编译和工作。链接的补丁还修改了boost/spirit/home/phoenix/fusion.hpp,当然也包括了这个新的头文件。


感谢您的详细回复(当然还有支持票)! - Lyth
这次我在Trac问题跟踪器上报告了功能请求。https://svn.boost.org/trac/boost/ticket/7199 - 我相信这次不会被忘记。我喜欢Phoenix通常都是“电池内置”的(即无需调整算法和容器访问器),所以这确实感觉像是一个疏忽。 - sehe
感谢补丁。还应该提到 OP 的代码需要改用 boost::phoenix::at_key。一旦你理解了,就很明显! - Epimetheus

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