【问题标题】:Error compiling a custom container for boost spirit为提升精神编译自定义容器时出错
【发布时间】:2014-11-23 18:36:51
【问题描述】:

我想解析如下内容:

1;2
=1200
3;4
5;6

行可以以任何顺序出现。以 = 符号开头的行可以不止一条,只有最后一条很重要;包含 ; 的行表示我要存储在地图中的一对值。阅读this question 的答案,我想出了一些应该足够好的代码(对不起,但我仍然是 Spirit 的菜鸟)并且应该做我想要实现的目标。代码如下:

#define BOOST_SPIRIT_USE_PHOENIX_V3
#define DATAPAIR_PAIR

#include <iostream>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapted/std_pair.hpp>
#include <boost/mpl/bool.hpp>
#include <map>
#if !defined(DATAPAIR_PAIR)
#include <vector>
#endif


static const char g_data[] = "1;2\n=1200\n3;4\n5;6\n";
typedef std::string DataTypeFirst;

#if defined(DATAPAIR_PAIR)
typedef std::string DataTypeSecond;
typedef std::pair<DataTypeFirst, DataTypeSecond> DataPair;
typedef std::map<DataTypeFirst, DataTypeSecond> DataMap;
#else
typedef std::vector<DataTypeFirst> DataPair;
typedef std::map<DataTypeFirst, DataTypeFirst> DataMap;
#endif

struct MyContainer {
    DataMap data;
    double number;
};

namespace boost { namespace spirit { namespace traits {
    template<> struct is_container<MyContainer> : boost::mpl::true_ {};

    template<>
    struct container_value<MyContainer> {
        typedef boost::variant<double, DataPair> type;
    };

    template <>
    struct push_back_container<MyContainer, double> {
        static bool call ( MyContainer& parContainer, double parValue ) {
            parContainer.number = parValue;
            return true;
        }
    };

    template <>
    struct push_back_container<MyContainer, DataPair> {
        static bool call ( MyContainer& parContainer, const DataPair& parValue ) {
#if defined(DATAPAIR_PAIR)
            parContainer.data[parValue.first] = parValue.second;
#else
            parContainer.data[parValue[0]] = parValue[1];
#endif
            return true;
        }
    };
} } }

template <typename Iterator>
struct TestGrammar : boost::spirit::qi::grammar<Iterator, MyContainer()> {

    TestGrammar ( void );
    boost::spirit::qi::rule<Iterator, MyContainer()> start;
    boost::spirit::qi::rule<Iterator, DataPair()> data;
    boost::spirit::qi::rule<Iterator, double()> num;
};

template <typename Iterator>
TestGrammar<Iterator>::TestGrammar() :
    TestGrammar::base_type(start)
{
    using boost::spirit::qi::alnum;
    using boost::spirit::qi::lit;
    using boost::spirit::ascii::char_;;
    using boost::spirit::qi::double_;
    using boost::spirit::qi::eol;
    using boost::spirit::qi::eoi;

    start %= *((num | data) >> (eol | eoi));
    data = +alnum >> lit(";") >> +alnum;
    num = '=' >> double_;
}

int main() {
    std::cout << "Parsing data:\n" << g_data << "\n";

    TestGrammar<const char*> gramm;
    MyContainer result;
    boost::spirit::qi::parse(static_cast<const char*>(g_data),
        g_data + sizeof(g_data) / sizeof(g_data[0]) - 1,
        gramm,
        result
    );
    std::cout << "Parsed data:\n";
    std::cout << "Result: " << result.number << "\n";
    for (const auto& p : result.data) {
        std::cout << p.first << " = " << p.second << '\n';
    }

    return 0;
}

我在 Gentoo Linux 上开发这个,使用 dev-libs/boost-1.55.0-r2:0/1.55​​.0 和 gcc (Gentoo 4.8.3 p1.1, pie-0.5.9) 4.8.3 .编译上面的代码我得到一个类似的错误

/usr/include/boost/spirit/home/support/container.hpp:278:13: 错误:‘struct MyContainer’没有名为‘insert’的成员

作为一种解决方法,我想出了通过注释“#define DATAPAIR_PAIR”行获得的替代代码。在这种情况下,代码可以编译并工作,但我真正想要的是一对,例如我可以混合 std::string 和 int 值。为什么使用 std::pair 作为我的数据规则的属性会导致编译器错过 push_back_container 的正确特化?是否可以使用 std::pair 或任何等效方法修复代码并使其正常工作?

【问题讨论】:

  • This 有效,但远非漂亮。唯一的添加标有//ADDED。可悲的是,我不知道为什么在您的情况下需要它,而不是在链接的示例或您的解决方法中。
  • @cv_and_he 好时机
  • @cv_and_he 这是一个非常奇怪的要求(实际上,这是有道理的,但从某种意义上说它并不总是必需的)。不错的工作。我无法让它工作,所以被你的解决方案欺骗了:)

标签: c++ boost-spirit


【解决方案1】:

我会通过 /just/ 不同时处理像容器和非容器这样的东西来简化这一点。所以对于这种特殊情况,我可能会偏离我通常的口头禅(avoid semantic actions)并使用它们¹:

Live On Coliru

template <typename It, typename Skipper = qi::blank_type>
struct grammar : qi::grammar<It, MyContainer(), Skipper> {
    grammar() : grammar::base_type(start) {
        update_number = '=' > qi::double_ [ qi::_r1 = qi::_1 ];
        map_entry     = qi::int_ > ';' > qi::int_;

        auto number = phx::bind(&MyContainer::number, qi::_val);
        auto data   = phx::bind(&MyContainer::data, qi::_val);

        start         = *(
            (   update_number(number) 
              | map_entry [ phx::insert(data, phx::end(data), qi::_1) ]
            )
            >> qi::eol);
    }

  private:
    qi::rule<It, void(double&),       Skipper> update_number;
    qi::rule<It, MyContainer::Pair(), Skipper> map_entry;
    qi::rule<It, MyContainer(),       Skipper> start;
};

如果您能负担得起地图中的 (0;0) 条目,您甚至可以省去语法:

Live On Coliru

std::map<int, int> data;
double number;

bool ok = qi::phrase_parse(f, l, 
       *(
            (qi::omit['=' > qi::double_ [phx::ref(number)=qi::_1]] 
          | (qi::int_ > ';' > qi::int_)
        ) >> qi::eol)
        , qi::blank, data);

我也可以尝试让你的“先进精神”方法发挥作用,但可能需要一段时间 :)


¹ 为了便于阅读,我使用auto,但当然你不需要使用它;只需重复内联子表达式或使用 BOOST_AUTO。请注意,这对于有状态解析器表达式通常不是好的建议(请参阅 BOOST_SPIRIT_AUTO)

【讨论】:

  • 这可能是一个更好(更理智?)的解决方案。
  • 要么这样,要么我只好硬着头皮解析输入是什么,然后然后解释它以找到number字段的最后一个值跨度>
  • 感谢您的回答和 cmets。因此,为了使我的问题更具体,我正在尝试将 cuesheet 解析器实现为练习(如 .cue/.bin 中,请参阅 wikipedia 上的“cue sheet”)。在这种情况下,异常的部分是“FILE ”,我将名称/类型存储为字符串/枚举,但我也遇到了案例字符串/字符串的问题。在像 cuesheet 这样的情况下,最好的方法是什么? sehe 建议的语义操作或访问者或 cv_and_he 建议的 2-pass 解析?
  • 我只是按原样阅读文件。提示表并不大,您可以只表示内存中的冗余信息。哎呀,它甚至可以帮助您检测某些媒体播放器/组织者在这种“标准”格式的特定实现中开发的许多怪癖。通过这种方式,您可以获得最简单的解析器,没有特征地狱,没有语义动作尽可能丰富的数据表示。
猜你喜欢
  • 2014-03-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多