Can I read the file and build heterogeneous objects at compile time?
Situation:
YAML file containing a list of heterogeneous objects by name:
object: Foo
name: Joe Bloggs
age: 26
object: Bar
location: UK
Objects do not inherit from any base class or do not share any relationship with each other, except that they appear to be "alive" together.
It can contain any number of objects. The list of available types can exist in the type list in the codebase, if required.
In my C ++ land, I have objects:
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;
};
And when compiling, I want to turn this YAML file into boost::fusion::vector
:
boost::fusion::vector<Foo, Bar>(Foo("Joe Bloggs", 26), Bar("UK"));
Or:
boost::fusion::vector<Foo, Bar>(make_obj<Foo>("Joe Bloggs", 26), make_obj<Bar>("UK"));
It may also be std::tuple
if it makes life easier.
Specializations for make_obj can exist for all supported objects, if needed.
Is it possible?
Wanting to get my hands dirty with MPL / other advanced metaprogramming if needed, or can I do it all with constexpr?
The C ++ version is not a concern, can use the Clang C ++ 14 trunk if needed.
source to share
I see two main approaches:
With Compiletime "Reflection"
You can use BOOST_FUSION_ADAPT_STRUCT and have your cake and eat it. If you adapt your structs, you can iterate over them statically - actually writing this code generator that @ πάνταῥεῖ mentions, but is built into C ++ code and at compile time.
You can have types statically constrained with a variant.
- Is it possible to generate a merge map from an adapted structure? shows how to use
fusion::extension::struct_member_name
to create generic structures adapted to Fusion (you can forget about the type demangling name because you don't require it) - Increase the merge sequence type and name identification for structs and class , which shows XML-like output for adapted Fusion structs.
With manual grammar
Using Boost Spirit, you can simply create a grammar for the same structures:
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_;
This is now parsed into variant<Foo, Bar>
without further coding. It even allows name
and age
appears randomly (or accept the default). Of course, if you don't want this flexibility, replace ^
with >>
in the grammar.
Here's an example input:
object: Foo
name: Joe Bloggs
age: 26
object: Foo
age: 42
name: Douglas Adams
object: Foo
name: Lego Man
object: Bar
location: UK
And here is the output of the debug output:
<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";
}
source to share