Feb ’10 08

Here is another question raised from time to time: “I know how to use a plain struct as an attribute for a sequence parser in Qi by adapting it with BOOST_FUSION_ADAPT_STRUCT. Unfortunately this does not work if the struct is a template. What can I do in this case?”.

There have been plans for a while to create a separate Fusion facility BOOST_FUSION_ADAPT_TPL_STRUCT allowing to adapt templated data types, but this is not in place yet. Today I will describe a trick you can apply to adapt your templates into ‘proper’ Fusion sequences anyway.

We will use the fact that a Qi grammar is already a template in most cases, and even if it is not a template yet, it can be easily converted into one. Further we will use the built-in capability of rule’s to invoke a custom attribute transformation if the attribute type of the right hand side does not exactly match the left hand side’s attribute type.

Let us assume this to be our data structure we want to fill while parsing:

template <typename A, typename B>
struct data
{
    A a;
    B b;
};

We would like to be able to directly utilize this template type as an attribute for our grammar. A possible way of adapting the template type to make it usable as a Fusion sequence is to define a fusion::vector<A&, B&> and initialize it with the references to the data members of our template type. If we the pass this Fusion vector as the attribute to the actual parser expression we effectively supply our original data members as the attributes to the parsing process.

namespace qi = boost::spirit::qi;
namespace fusion = boost::fusion;

template <typename Iterator, typename A, typename B>
struct data_grammar : qi::grammar<Iterator, data<A, B>()>
{
    data_grammar() : data_grammar::base_type(start)
    {
        // the implicit attribute transformation 'adapts' data<> to
        // the Fusion vector
        start = real_start;

        // do the actual parsing of the data<> members
        real_start = qi::auto_ >> ',' >> qi::auto_;
    }

    qi::rule<Iterator, data<A, B>()> start;
    qi::rule<Iterator, fusion::vector<A&, B&>()> real_start;
};

The signature of the grammar’s start rule has to match the signature of the grammar itself. To accommodate for this we introduce a second rule ‘real_start’ dedicated to the parsing of our data members. At the same time this allows us to inject the needed transformation of our data<> attribute to the Fusion vector. As the left hand side’s and right hand side’s attribute types do not match, the parser expression start = real_start will invoke Spirit’s customization point transform_attribute. But since the default implementation of this customization point does not handle our special data types the way we want, we are required to implement our own specialization:

namespace boost { namespace spirit { namespace traits
{
    template <typename A, typename B>
    struct transform_attribute<data<A, B>, fusion::vector<A&, B&> >
    {
        typedef fusion::vector<A&, B&> type;
        static type pre(data<A, B>& val) { return type(val.a, val.b); }
        static void post(data<A, B>&, fusion::vector<A&, B&> const&) {}
        static void fail(data<A, B>&) {}
    };
}}}

The function pre() is called before the right hand side parser expression is invoked. It gets passed the left hand side’s attribute (the data<> instance) and is required to return the attribute to be passed to the rule’s right hand side expression. The returned Fusion vector is initialized with the references to the data members of our original data<> instance. The functions post() and fail() can be left empty in our case. For more information about this customization point please see the corresponding documentation here.

I added a new example to Spirit demonstrating this technique. Currently, it can be accessed from the Boost SVN only (see adapt_template_struct.cpp), but in the future it will be released as part of Spirit.

Just in case you were wondering: yes, this trick works equally well for Karma generators. The only difference is that the members of the created Fusion vector will have to be constant references instead.

5 Responses to “How to Adapt Templates as a Fusion Sequence”

  1. I believe for backwards compatibility you should #include <boost/spirit/include/version.hpp> and wrap the fail function in a #if SPIRIT_VERSION >= 0x2030 .. #endif

    • Hartmut Kaiser says:

      Jeroen,

      you’re right, fail() will be added with the next version only (Boost 1.43/Spirit V2.3), but it doesn’t hurt to define that function anyways, it just won’t be called.

      Thanks!
      Regards Hartmut

  2. Olaf says:

    Is the

    typedef fusion::vector<A&, B> type 
    

    correct? Isn’t it

    typedef fusion::vector<A&, B&> type ?
    

    Thanks,
    Olaf

Leave a Reply

To use RetinaPost you must register at http://www.RetinaPost.com/register