I have a multiple classes each with different member variables that are initialized trivially in a constructor. Here is an example:
struct Person
{
Person(const char *name, int age)
:
name(name),
age(age)
{
}
private:
const char *name;
int age;
};
Each has an associated print<>()
function.
template <>
void print<Person>(const Person &person)
{
std::cout << "name=" << name << "\n";
std::cout << "age=" << age << "\n";
}
This code is error prone since the parameter list is replicated in four places. How can I rewrite the code to avoid this duplication? I'd like to use the preprocessor and/or templates.
For example, could I use the X-args preprocessor technique -- something like this?
#define ARGUMENTS \
ARG(const char *, name) \
ARG(int, age)
struct Person
{
Person(LIST_TYPE_NAME_COMMA(ARGUMENTS))
:
LIST_NAME_INIT(ARGUMENTS)
{
}
private:
LIST_TYPE_NAME_SEMICOLON(ARGUMENTS)
};
template <>
void print<Person>(const Person &person)
{
LIST_COUT_LINE(ARGUMENTS)
}
#undef ARGUMENTS
Or better, a template-based approach?
Please don't question why I want to do this, there are reasoned design decisions that have resulted in multiple similar objects with named parameters. The parameters need to be named member variables for performance reasons. I'm just exploring whether it's possible to list the parameters and their types only once.
What you need to do is have the preprocessor generate reflection data about the fields. This data can be stored as nested classes.
First, to make it easier and cleaner to write it in the preprocessor we will use typed expression. A typed expression is just an expression that puts the type in parenthesis. So instead of writing int x
you will write (int) x
. Here are some handy macros to help with typed expressions:
#define REM(...) __VA_ARGS__
#define EAT(...)
// Retrieve the type
#define TYPEOF(x) DETAIL_TYPEOF(DETAIL_TYPEOF_PROBE x,)
#define DETAIL_TYPEOF(...) DETAIL_TYPEOF_HEAD(__VA_ARGS__)
#define DETAIL_TYPEOF_HEAD(x, ...) REM x
#define DETAIL_TYPEOF_PROBE(...) (__VA_ARGS__),
// Strip off the type
#define STRIP(x) EAT x
// Show the type without parenthesis
#define PAIR(x) REM x
Next, we define a REFLECTABLE
macro to generate the data about each field(plus the field itself). This macro will be called like this:
REFLECTABLE
(
(const char *) name,
(int) age
)
So using Boost.PP we iterate over each argument and generate the data like this:
// A helper metafunction for adding const to a type
template<class M, class T>
struct make_const
{
typedef T type;
};
template<class M, class T>
struct make_const<const M, T>
{
typedef typename boost::add_const<T>::type type;
};
#define REFLECTABLE(...) \
static const int fields_n = BOOST_PP_VARIADIC_SIZE(__VA_ARGS__); \
friend struct reflector; \
template<int N, class Self> \
struct field_data {}; \
BOOST_PP_SEQ_FOR_EACH_I(REFLECT_EACH, data, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))
#define REFLECT_EACH(r, data, i, x) \
PAIR(x); \
template<class Self> \
struct field_data<i, Self> \
{ \
Self & self; \
field_data(Self & self) : self(self) {} \
\
typename make_const<Self, TYPEOF(x)>::type & get() \
{ \
return self.STRIP(x); \
}\
typename boost::add_const<TYPEOF(x)>::type & get() const \
{ \
return self.STRIP(x); \
}\
const char * name() const \
{\
return BOOST_PP_STRINGIZE(STRIP(x)); \
} \
}; \
What this does is generate a constant fields_n
that is number of reflectable fields in the class. Then it specializes the field_data
for each field. It also friends the reflector
class, this is so it can access the fields even when they are private:
struct reflector
{
//Get field_data at index N
template<int N, class T>
static typename T::template field_data<N, T> get_field_data(T& x)
{
return typename T::template field_data<N, T>(x);
}
// Get the number of fields
template<class T>
struct fields
{
static const int n = T::fields_n;
};
};
Now to iterate over the fields we use the visitor pattern. We create an MPL range from 0 to the number of fields, and access the field data at that index. Then it passes the field data on to the user-provided visitor:
struct field_visitor
{
template<class C, class Visitor, class T>
void operator()(C& c, Visitor v, T)
{
v(reflector::get_field_data<T::value>(c));
}
};
template<class C, class Visitor>
void visit_each(C & c, Visitor v)
{
typedef boost::mpl::range_c<int,0,reflector::fields<C>::n> range;
boost::mpl::for_each<range>(boost::bind<void>(field_visitor(), boost::ref(c), v, _1));
}
Now for the moment of truth we put it all together. Here is how we can define the Person
class:
struct Person
{
Person(const char *name, int age)
:
name(name),
age(age)
{
}
private:
REFLECTABLE
(
(const char *) name,
(int) age
)
};
Here is the generalized print_fields
function:
struct print_visitor
{
template<class FieldData>
void operator()(FieldData f)
{
std::cout << f.name() << "=" << f.get() << std::endl;
}
};
template<class T>
void print_fields(T & x)
{
visit_each(x, print_visitor());
}
An example:
int main()
{
Person p("Tom", 82);
print_fields(p);
return 0;
}
Which outputs:
name=Tom
age=82
And voila, we have just implemented reflection in C++, in under 100 lines of code.
I have solved the same problem with my generic struct to JSON code.
Define a macro: REFLECT( CLASS_NAME, MEMBER_SEQUENCE ) where MEMBER_SEQUENCE is (name)(age)(other)(...)
Have REFLECT expand to something similar to:
template<>
struct reflector<CLASS_NAME> {
template<typename Visitor>
void visit( Visitor&& v ) {
v( "name" , &CLASS_NAME::name );
v( "age", &CLASS_NAME::age );
...
}
}
You can use BOOST_PP_SEQ_FOREACH to expand the SEQ into the visitors.
Then define your print visitor:
template<typename T>
struct print_visitor {
print_visitor( T& s ):self(s){}
template<typename R>
void operator( const char* name, R (T::*member) )const {
std::cout<<name<<"= "<<self.*member<<std::endl;
}
T& self;
}
template<typename T>
void print( const T& val ) {
reflector<T>::visit( print_visitor<T>(val) );
}
http://bytemaster.github.com/mace/group_mace_reflect__typeinfo.html
https://github.com/bytemaster/mace/blob/master/libs/reflect/include/mace/reflect/reflect.hpp
I am afraid that your solution is pretty optimal for this reduced usecase. Where we can help is if you have additional functions besides print
that would benefit from iterating over the fields.
This is a perfect example for Boost.Fusion Fusion Sequences; they can be used to introduce compile-time reflection. On top of it you can then generate more generic runtime behavior.
So, you can for example declare your elements using a Fusion.Map (which restricts you to a single occurrence of each type) or other such fantasies.
If your type does not conform to a Fusion Sequence (or you don't want to meddle with its internals), there are adapters in the adapted section such as BOOST_FUSION_ADAPT_STRUCT
. And of course, since not everything is a struct
(or has public members), there is also a more generic version for classes, it just gets ugly soon: BOOST_FUSION_ADAPT_ADT
.
Stealing from the quick start:
struct print_xml {
template <typename T>
void operator()(T const& x) const {
std::cout
<< '<' << typeid(x).name() << '>'
<< x
<< "</" << typeid(x).name() << '>'
;
}
};
int main() {
vector<int, char, std::string> stuff(1, 'x', "howdy");
int i = at_c<0>(stuff);
char ch = at_c<1>(stuff);
std::string s = at_c<2>(stuff);
for_each(stuff, print_xml());
}
The adapters will let you "adapt" a type, so you would get:
struct Foo { int bar; char const* buzz; };
BOOST_FUSION_ADAPT_STRUCT(
Foo,
(int, bar)
(char const*, buzz)
)
And then:
int main() {
Foo foo{1, "Hello");
for_each(foo, print_xml());
}
It's a pretty impressive library :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With