How to avoid boost::phoenix when generating with boost::spirit::karma

戏子无情 提交于 2019-12-06 14:50:05
sehe

You can't use boost::bind expressions as a phoenix actor.

With regards to the long mangled names, I wouldn't be surprised if the use of other TMP-heavy libraries contribute significantly (e.g. Boost Range, which has it's own forest of template instantiations returning views from adaptors etc.).

You could try

  1. Adapting the Item struct to a fusion sequence: (with or without attr_cast<>)

    • Along similar same lines, you could make the item rule itself consume a fusion sequence directly
  2. To 'precook' an actor for use in the semantic action, either

    • using a Polymorphic Function Object (a.k.a. deferred calleable object)
    • or using a free function
  3. Bake custom phoenix::function<> actors for getId() and getValues() (for completeness)

In the following, I demonstrate all of the approaches, concluding with a full sample program that includes all of these options, and was compiled with gcc 4.5.3 on windows (Cygwin).


1. Adapting the Item struct to a fusion sequence:

BOOST_FUSION_ADAPT_ADT(Item,
        (std::string,         std::string,         obj.getId(),     (void)val)
        (Item::Values const&, Item::Values const&, obj.getValues(), (void)val)
    )

// the rule becomes simply
item =
    lit("<item>")
    << id      // yay for fusion magic!
    << values
    << lit("</item>");

This might actually make things equally bad internally - it's hard to tell from my vantage point. If it does, you could try to pry apart the phase where the attributes are consumed in the parse-expressions and the place where the fusion adapter proxies are evaluated:

item =
    lit("<item>")
    << karma::attr_cast<boost::fusion::vector<std::string, Item::Values> > // cast up front
    (
           id     // directly
        << values // 
    )
    << lit("</item>");
  • Along similar same lines, you could make the item rule itself consume a fusion sequence directly instead of an Item instance. This is sure to remove all of the bind/proxy complexity from the parser expressions, but it requires more work to get the rest of the generators to connect.

2. to 'precook' an actor for use in the semantic action, either

  • using a Polymorphic Function Object (a.k.a. deferred calleable object):

    struct deferredGetId
    {
        template<typename,typename,typename> struct result { typedef void type; };
    
        template<typename Attr, typename Ctx, typename Bool>
        void operator()(Attr& attribute, Ctx const& context, Bool& flag) const
        {
            attribute = boost::fusion::at_c<0>(context.attributes).getId();
            flag = true;
        }
    };
    
  • or using a free function. This then requires hard-coding the template argument for Context. A helpful explanation of the role of Spirit Context in karma rules may be found here: boost spirit semantic action parameters

    // non-template free function
    void hardcodedGetId(std::string& attribute, 
            boost::spirit::context<boost::fusion::cons<const Item&, boost::fusion::nil>, boost::fusion::vector0<> > const& context, 
            bool& flag)
    {
        attribute = boost::fusion::at_c<0>(context.attributes).getId();
        flag = true;
    }
    
  • Note that there is also BOOST_PHOENIX_ADAPT_FUNCTION that allows you to use a function template directly, but all that really does is wrap the function template inside a new Polymorphic Function Object type like above, so this will not gain you anything.

Now you could use those in your rules:

 item =
     lit("<item>")
     << id [_1 = phoenix::bind(&Item::getId, _val)] // works fine on GCC :)
     << id [deferredGetId()] // approach #1 (a)
     << id [hardcodedGetId]  // approach #1 (b)
     << values[_1 = phoenix::bind(&Item::getValues, _val)]
     << lit("</item>");

3. Using phoenix::function to wrap deferred callables

Finally there is the option to use phoenix::function to wrap deferred callables directly as a (unary) actor. I'm not convinced it will actually help you, unless there is some kind of erasure going on that I'm not aware of. But the end result is pretty elegant, which in itself is a good reason to mention it, if only for completeness:

struct GetId
{
    template<typename> struct result { typedef std::string type; };
    template<typename Item>
    std::string operator()(Item const& item) const { return item.getId(); }
};

struct GetValues
{
    template<typename> struct result { typedef Item::Values type; };
    template<typename Item>
    typename Item::Values const& operator()(Item const& item) const { return item.getValues(); }
};

boost::phoenix::function<GetId>     phx_getId;
boost::phoenix::function<GetValues> phx_getValues;

This, you can then employ simply like this:

item =
    lit("<item>")
    << id[_1 = phx_getId(_val)]
    << values[_1 = phx_getValues(_val)]
    << lit("</item>");

Full sample code

#include <boost/config/warning_disable.hpp>

#include <boost/foreach.hpp>
#include <boost/assign/list_of.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm.hpp>

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/phoenix.hpp>

#include <boost/bind.hpp>

#include <iostream>
#include <string>
#include <list>

#include <boost/fusion/adapted.hpp>

namespace karma = boost::spirit::karma;
namespace spirit = boost::spirit;
namespace ascii = boost::spirit::ascii;
namespace phoenix = boost::phoenix;

class Item
{
public:
    typedef std::vector<int> Values;

    Item(const std::string  & i, const Values & v) : m_id(i), m_values(v) {}
    std::string getId() const { return m_id; }
    const Values & getValues() const { return m_values; }

private:
    std::string m_id;
    Values m_values;
};

class ItemList
{
public:
    typedef std::map<std::string, Item> Items;

    ItemList() {}
    ItemList(const Items & s, const Items & o) : m_some(s), m_other(o) {}
    const Items getSome() const { return m_some; }
    const Items getOther() const { return m_other; }

private:
    Items m_some;;
    Items m_other;
};

/////////////////////////////////////////////
// 1. Adapting the `Item` struct

BOOST_FUSION_ADAPT_ADT(Item,
        (std::string,         std::string,         obj.getId(),     (void)val)
        (Item::Values const&, Item::Values const&, obj.getValues(), (void)val)
    )

/////////////////////////////////////////////
// 2. Precooking Actors

struct deferredGetId
{
    template<typename,typename,typename> struct result { typedef void type; };

    template<typename Attr, typename Ctx, typename Bool>
    void operator()(Attr& attribute, Ctx const& context, Bool& flag) const
    {
        attribute = boost::fusion::at_c<0>(context.attributes).getId();
        flag = true;
    }
};

// non-template free function
void hardcodedGetId(std::string& attribute, 
        boost::spirit::context<boost::fusion::cons<const Item&, boost::fusion::nil>, boost::fusion::vector0<> > const& context, 
        bool& flag)
{
    attribute = boost::fusion::at_c<0>(context.attributes).getId();
    flag = true;
}

/////////////////////////////////////////////
// 3. phoenix::function

struct GetId
{
    template<typename> struct result { typedef std::string type; };
    template<typename Item>
    std::string operator()(Item const& item) const { return item.getId(); }
};

struct GetValues
{
    template<typename> struct result { typedef Item::Values type; };
    template<typename Item>
    typename Item::Values const& operator()(Item const& item) const { return item.getValues(); }
};

boost::phoenix::function<GetId>     phx_getId;
boost::phoenix::function<GetValues> phx_getValues;

template <typename Iterator>
struct list_generator : karma::grammar<Iterator, ItemList()>
{
    list_generator(const ItemList & i)
        : list_generator::base_type(start)
{
    using karma::int_;
    using karma::_1;
    using karma::lit;
    using karma::_val;

    // using phoenix causes: fatal error LNK1179: invalid or corrupt file:
    // duplicate COMDAT
    // '?value@?$result_@U?$member_variable@$$A6E?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZP8Item@@AE?AV12@XZ@detail@phoenix@boost@@@?$is_mem_fun_pointer_select@$0A@@detail@boost@@2_NB'
    // this is probably because the symbol names are too long.

    // Convert maps into lists containing only the values
    const Items some  = boost::copy_range<Items>(i.getSome() | boost::adaptors::map_values);
    const Items other = boost::copy_range<Items>(i.getOther() | boost::adaptors::map_values);

    id =
        lit("<id>")
        << karma::string
        << lit("</id>");

    values =
        lit("<values>") 
        << (int_ % ';') 
        << lit("</values>");

    item =
        lit("<item>")
     //
        << id[_1 = phoenix::bind(&Item::getId, _val)] // works fine on GCC :)
        << id [deferredGetId()]     // approach #2 (a)
        << id [hardcodedGetId]      // approach #2 (b)
        << id [_1= phx_getId(_val)] // approach #3
     //
        << values[_1 = phoenix::bind(&Item::getValues, _val)]
        << values[_1 = phx_getValues(_val)]
        << lit("</item>");

    item =
        lit("<item>")
        << id     // approach #1: using BOOST_FUSION_ADAPT_ADT
        << values // approach #1: using BOOST_FUSION_ADAPT_ADT
        << lit("</item>");

    // approach #2 _with_ attr_cast:
    item =
        lit("<item>")
        << karma::attr_cast<boost::fusion::vector<std::string, Item::Values> >
        (
               id     // 'native' fusion sequence access
            << values // 'native' fusion sequence access
        )
        << lit("</item>");

    start =
        lit("<some>")     << (*item)[_1 = some] << lit("</some>")
        << lit("<other>") << (*item)[_1 = other] << lit("</other>");
}

typedef std::vector<Item> Items;
karma::rule<Iterator, std::string()> id;
karma::rule<Iterator, Item::Values()> values;
karma::rule<Iterator, Item()> item;
karma::rule<Iterator, ItemList()> start;
};

int main()
{
    const Item::Values values = boost::assign::list_of(1)(2)(3);
    const Item a("a", values);
    const Item b("b", values);

    ItemList::Items some, other;
    some.insert(std::make_pair(a.getId(), a));
    other.insert(std::make_pair(b.getId(), b));
    const ItemList items(some, ItemList::Items());

    typedef std::back_insert_iterator<std::string> Iter;
    typedef list_generator<Iter> Generator;

    Generator grammar(items);

    std::string generated;
    Iter sink(generated);
    if (!karma::generate(sink, grammar))
    {
        std::cout << "Generating failed\n";
    }
    else
    {
        std::cout << "Generated: " << generated << "\n";
    }

    return 0;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!