使用boost :: spirit :: karma生成时如何避免boost :: phoenix

时间:2013-05-17 10:51:56

标签: c++ boost boost-spirit boost-bind boost-phoenix

我是错误的受害者“LNK1179:文件无效或损坏:重复COMDAT” 并且these sources让我相信,通过不使用phoenix,我可以避免此错误。

(这是my previous question的后续行动。) 我想用其他东西替换boost::phoenix。也许是boost::bind,但我不知道如何才能让它访问karma::_val

以下代码无法使用

在VC9上编译
  

错误C2825:'F':后跟'::'

时必须是类或命名空间
#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>


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;
};

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)]
        << id[boost::bind(&Item::getId, &_val, _1)] // !! error C2825 !!
        << values[_1 = phoenix::bind(&Item::getValues, _val)]
        << 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;
}

完整的错误是:

error C2825: 'F': must be a class or namespace when followed by '::'
1>        c:\path\to\boost\boost/bind/bind_template.hpp(15) : see reference to class template instantiation 'boost::_bi::result_traits<R,F>' being compiled
1>        with
1>        [
1>            R=boost::_bi::unspecified,
1>            F=std::basic_string<char,std::char_traits<char>,std::allocator<char>> (__thiscall Item::* )(void) const
1>        ]
1>        .\spiritTest.cpp(85) : see reference to class template instantiation 'boost::_bi::bind_t<R,F,L>' being compiled
1>        with
1>        [
1>            R=boost::_bi::unspecified,
1>            F=std::string (__thiscall Item::* )(void) const,
1>            L=boost::_bi::list2<boost::_bi::value<boost::phoenix::actor<boost::phoenix::composite<boost::phoenix::reference_eval,boost::fusion::vector<boost::spirit::attribute<0>,boost::fusion::void_,boost::fusion::void_,boost::fusion::void_,boost::fusion::void_,boost::fusion::void_,boost::fusion::void_,boost::fusion::void_,boost::fusion::void_,boost::fusion::void_>>>>,boost::_bi::value<boost::spirit::_1_type>>
1>        ]
1>        .\spiritTest.cpp(57) : while compiling class template member function 'list_generator<Iterator>::list_generator(const ItemList &)'
1>        with
1>        [
1>            Iterator=Iter
1>        ]
1>        .\spiritTest.cpp(116) : see reference to class template instantiation 'list_generator<Iterator>' being compiled
1>        with
1>        [
1>            Iterator=Iter
1>        ]

1 个答案:

答案 0 :(得分:5)

你不能将boost::bind表达式用作凤凰演员。

关于长期错误的名称,如果使用其他TMP重型库有显着贡献(例如Boost Range,它有自己的模板实例化森林,从适配器等返回视图),我不会感到惊讶。 / p>

你可以尝试

  1. Item结构修改为融合序​​列 :(有或没有attr_cast<>

    • 沿着类似的相同行,您可以让item规则本身直接使用融合序​​列
  2. 要'预先'一个演员用于语义动作,

    • 使用多态函数对象(a.k.a。延迟可调用对象
    • 或使用免费功能
  3. phoenix::function<>getId()烘焙自定义getValues()演员(为了完整性)

  4. 在下文中,我将演示所有方法,最后是包含所有这些选项的完整示例程序,并在Windows(Cygwin)上使用gcc 4.5.3进行编译。


    1。使Item结构适应融合序​​列

    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>");
    

    这实际上可能会使内部同样糟糕 - 从我的观点来看很难说清楚。如果是这样,您可以尝试分开在解析表达式中使用属性的阶段和评估fusion adapter proxies的位置:

    item =
        lit("<item>")
        << karma::attr_cast<boost::fusion::vector<std::string, Item::Values> > // cast up front
        (
               id     // directly
            << values // 
        )
        << lit("</item>");
    
    • 沿着类似的相同行,您可以使item规则本身直接使用融合序​​列而不是Item实例。这是确定从解析器表达式中删除所有绑定/代理复杂性,它需要更多的工作才能让其余的生成器连接。

    2。 'precook'一个演员用于语义动作,

    • 使用多态函数对象(a.k.a。延迟可调用对象):

      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;
          }
      };
      
    • 或使用免费功能。然后,这需要对Context的模板参数进行硬编码。关于精神语境在业力规则中的作用的有用解释可以在这里找到: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;
      }
      
    • 请注意,还有BOOST_PHOENIX_ADAPT_FUNCTION允许您直接使用函数模板,但所有真正做的是将函数模板包装在新的多态函数对象类型中如上所述,所以这不会给你带来任何好处。

    现在您可以在规则中使用这些:

     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。使用phoenix::function包装延迟的callables

    最后,可以选择使用phoenix::function将延迟的callable直接包装为(一元)actor。我不相信它会真正帮助你,除非有某种 erasure 正在进行,我不知道。 但最终结果非常优雅,如果只是为了完整性,这本身就是提及它的一个很好的理由:

    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;
    

    这样,您可以像这样使用:

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

    完整示例代码

    #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;
    }