情况:
包含异构对象列表的 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++ 代码内联。
您可以使用变体对类型进行静态约束。
- Is it possible to generate a fusion map from an adapted struct?显示如何使用
fusion::extension::struct_member_name
转储使用 Fusion 改编的通用结构(您可以忘记类型名称分解,因为您不需要它) - Boost fusion sequence type and name identification for structs and class显示 Fusion 改编结构的类似 XML 的输出。
使用手动语法
使用 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>
无需任何进一步的编码。它甚至允许 name
和 age
以随机顺序出现(或接受默认值)。当然,如果您不想要这种灵 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)
#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/