c++ - 我可以在编译时读取文件并构造异构对象吗?

标签 c++ boost metaprogramming boost-fusion

情况:

包含异构对象列表的 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 的特化。

这可能吗?

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

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

最佳答案

我看到两种主要方法:

使用编译时“反射”

您可以使用 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<Foo, Bar>无需任何进一步的编码。它甚至允许 nameage以随机顺序出现(或接受默认值)。当然,如果您不想要这种灵 active ,请替换 ^>>在语法中。

这是一个示例输入:

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

关于c++ - 我可以在编译时读取文件并构造异构对象吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27551795/

相关文章:

c++ - 实现全功能的移位流语法

python - 如何在Python中委托(delegate) `__iter__`方法

ruby - 在 Ruby 中对实例方法进行猴子修补时注入(inject)外部作用域

c++ - 使用宏 : Class with members and constructor 生成代码

c++ - 同步访问分配的内存

c++ - 将文本文件中的行存储在字符串列表中

c++ - Boost Spirit : Error C2664, 无法将 'const boost::phoenix::actor<Eval>' 转换为 'char'

c++ - 如何使类对象成为C++中的检查条件?

c++ - boost asio streambuf 在调用 consume 后不释放内存?

c++ - 通过负面警告摆脱 gcc shift