【问题标题】:Can I read a file and construct hetereogenous objects at compile time?我可以在编译时读取文件并构造异构对象吗?
【发布时间】:2015-02-17 13:09:22
【问题描述】:

情况:

包含按名称列出的异构对象列表的 YAML 文件,如下所示:

object: Foo
  name: Joe Bloggs
  age: 26
object: Bar
  location: UK

对象不从任何基类继承或在彼此之间共享任何类型的关系,除了它们看起来“生活”在一起的事实。

这可以包含任意数量的对象。如果需要,可用类型的列表可以存在于代码库中的类型列表中。

在我的 C++ 领域,我有对象:

struct Foo {
  Foo(std::string n, int a) : name(n), age(a) {}

  std::string name;
  int age;
};

struct Bar {
  Bar(std::string l) : location(l) {}

  std::string location;
};

在编译时,我想将该 YAML 文件转换为 boost::fusion::vector

boost::fusion::vector<Foo, Bar>(Foo("Joe Bloggs", 26), Bar("UK"));

或者:

boost::fusion::vector<Foo, Bar>(make_obj<Foo>("Joe Bloggs", 26), make_obj<Bar>("UK"));

也可以是std::tuple,如果它能让生活更轻松。

如果需要,所有支持的对象都可以存在 make_obj 的特化。

这可能吗?

如果需要,我愿意使用 MPL/其他高级元编程,或者,我可以使用 constexpr 完成所有这些吗?

C++版本不用担心,如果需要可以使用trunk Clang C++14。

【问题讨论】:

  • 恐怕你需要为此编写一个小代码生成器。
  • 这看起来很有趣,虽然它似乎是在运行时发生的?我希望在编译时处理类型+空间要求。

标签: c++ boost metaprogramming boost-fusion


【解决方案1】:

我看到两种主要方法:

使用编译时“反射”

您可以使用 BOOST_FUSION_ADAPT_STRUCT 来吃蛋糕。如果你调整你的结构,你可以静态迭代它们——实际上是编写@πάνταῥεῖ提到的代码生成器,但在编译时与 C++ 代码内联。

您可以使用变体对类型进行静态约束。

使用手动语法

使用 Boost Spirit,您可以为相同的结构创建语法:

    start   = *(label_(+"object") >> object_);
    object_ = foo_ | bar_;

    foo_    = "Foo" >> eol >> (
                (string_prop_(+"name") >> eol) ^
                (int_prop_(+"age") >> eol)
            );

    bar_    = "Bar" >> eol >> (
                (string_prop_(+"location") >> eol)
            );

    label_  = lit(_r1) >> ':';

    string_prop_ = label_(_r1) >> lexeme [ *(char_ - eol) ];
    int_prop_    = label_(_r1) >> int_;

现在它无需任何进一步编码即可解析为variant&lt;Foo, Bar&gt;。它甚至允许nameage 以随机顺序出现(或接受默认值)。当然,如果您不想要这种灵活性,请将语法中的^ 替换为&gt;&gt;

这是一个示例输入:

object: Foo
  name: Joe Bloggs
  age: 26
object: Foo
  age: 42
  name: Douglas Adams
object: Foo
  name: Lego Man
object: Bar
  location: UK

这是示例(调试)输出的尾部:

<success></success>
<attributes>[[[[J, o, e,  , B, l, o, g, g, s], 26], [[D, o, u, g, l, a, s,  , A, d, a, m, s], 42], [[L, e, g, o,  , M, a, n], 0], [[U, K]]]]</attributes>
</start>
Parse success: 4 objects
N4data3FooE (Joe Bloggs 26)
N4data3FooE (Douglas Adams 42)
N4data3FooE (Lego Man 0)
N4data3BarE (UK)

Live On Coliru

#define BOOST_SPIRIT_DEBUG
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapted/struct.hpp>
#include <boost/bind.hpp>
#include <fstream>

namespace qi  = boost::spirit::qi;
namespace phx = boost::phoenix;

namespace demo {
    struct visitor : boost::static_visitor<> {
        template<typename Seq>
            void operator()(std::ostream& os, Seq const& seq) const {
                os << typeid(Seq).name() << "\t" << boost::fusion::as_vector(seq);
            }
    };
}

namespace data {
    struct Foo {
        Foo(std::string n="", int a=0) : name(n), age(a) {}

        std::string name;
        int age;
    };

    struct Bar {
        Bar(std::string l="") : location(l) {}

        std::string location;
    };

    using object  = boost::variant<Foo, Bar>;
    using objects = std::vector<object>;

    std::ostream& operator<< (std::ostream& os, object const& o) {
        boost::apply_visitor(boost::bind(demo::visitor(), boost::ref(os), _1), o);
        return os;
    }
}

BOOST_FUSION_ADAPT_STRUCT(data::Foo,(std::string,name)(int,age))
BOOST_FUSION_ADAPT_STRUCT(data::Bar,(std::string,location))

template <typename It>
struct grammar : qi::grammar<It, data::objects(), qi::blank_type> {
    grammar() : grammar::base_type(start) {
        using namespace qi;

        start   = *(label_(+"object") >> object_);
        object_ = foo_ | bar_;

        foo_    = "Foo" >> eol >> (
                    (string_prop_(+"name") >> eol) ^
                    (int_prop_(+"age") >> eol)
                );

        bar_    = "Bar" >> eol >> (
                    (string_prop_(+"location") >> eol)
                );

        label_  = lit(_r1) >> ':';

        string_prop_ = label_(_r1) >> lexeme [ *(char_ - eol) ];
        int_prop_    = label_(_r1) >> int_;

        BOOST_SPIRIT_DEBUG_NODES((start)(object_)(foo_)(bar_)(label_)(string_prop_)(int_prop_));
    }
  private:
    qi::rule<It, data::objects(), qi::blank_type> start;
    qi::rule<It, data::object(),  qi::blank_type> object_;
    qi::rule<It, data::Foo(),     qi::blank_type> foo_;
    qi::rule<It, data::Bar(),     qi::blank_type> bar_;

    qi::rule<It, std::string(std::string), qi::blank_type> string_prop_;
    qi::rule<It, int(std::string), qi::blank_type>         int_prop_;
    qi::rule<It, void(std::string), qi::blank_type>        label_;
};

int main()
{
    using It = boost::spirit::istream_iterator;
    std::ifstream ifs("input.txt");
    It f(ifs >> std::noskipws), l;

    grammar<It> p;
    data::objects parsed;
    bool ok = qi::phrase_parse(f,l,p,qi::blank,parsed);
    if (ok)
    {
        std::cout << "Parse success: " << parsed.size() << " objects\n";
        for(auto& object : parsed)
            std::cout << object << "\n";
    } else
    {
        std::cout << "Parse failed\n";
    }

    if (f!=l)
        std::cout << "Remaining unparsed input: '" << std::string(f,l) << "'\n";
}

【讨论】:

  • 这也是我的第一个想法,我也很喜欢,但我犹豫了,因为忽略间距似乎非常不利于 YAML。
  • @MooingDuck 我不知道 YAML 是什么。当然,这在很大程度上是无关紧要的,因为很容易使语法成为非空白忽略。我展示的是概念,而不是语法(我猜那是为了 OP)
  • 他的输入是YetAnotherMarkupLanguage,它使用缩进来判断哪些条目是每个条目的“子项”。实际上,这可能很好地利用了他的特定输入。它只会错误处理格式错误的输入。
  • 我觉得那个链接有点意思。我很确定他不是在问“如何解析 YAML”。所以我会把它作为练习留给读者。
  • 是的,这很公平
猜你喜欢
  • 1970-01-01
  • 2013-09-04
  • 1970-01-01
  • 2011-12-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多