65

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.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
paperjam
  • 8,321
  • 12
  • 53
  • 79
  • 2
    Can you use C++11 features? Because you might want to look into initializer lists and/or uniform initialization; they might help you address this problem (see: https://en.wikipedia.org/wiki/C%2B%2B11#Core_language_usability_enhancements) – piwi Jun 14 '12 at 10:23
  • its error prone to store a pointer to the string instead of the string itself since you will at all times make sure the pointer doesn't go bad i.e. introducing a dependency. better to use std::string to store a copy of 'name' – AndersK Jun 14 '12 at 10:26
  • 2
    @AndersK the `const char *` is just for the sake of the example – paperjam Jun 14 '12 at 10:28
  • @piwi can use C++11 but prefer not to - initializer features are interesting but I don't think they solve this problem – paperjam Jun 14 '12 at 10:30
  • 8
    Speaking as someone who has done a fair chunk of maintenance on a macro-heavy C++ application... please, please, please don't write your own language using macros. They're a pain to work with, a pain to refactor and a pain for new devs to understand. You're not writing C++ in your example, you're writing your own proprietary DSL. Of course, this is an aesthetic/managerial point of view rather than a purely technical one. – Rook Jun 14 '12 at 10:49
  • 2
    @Rook I share your concerns and also avoid macros where possible. I don't mind a DSL since it sweetens the syntax of my `Person`-like classes and the goal here is to remove error-prone repetition of the same information. – paperjam Jun 14 '12 at 10:51
  • 7
    I feel that you'd be better served by writing a class spec in some relatively simple declarative fashion (XML, perhaps) and then using a script to generate the boilerplate for you. This has the benefit of using two standard languages (C++ and XML) and the loss, removal or replacement of the XML/script side need not render the C++ useless or unintelligible. Perhaps this solution is a little too heaviweight for you, but your future self will thank me when they have to make changes to it in a couple of years time ;-) – Rook Jun 14 '12 at 10:54
  • Wouldn't, for example, forgetting to print one member be caught immediately by a unit test? – Bo Persson Jun 14 '12 at 11:11
  • @Bo - You might also have forgotten to update the unit test! – paperjam Jun 14 '12 at 11:22
  • @paperjam: if you haven't changed the tests, you have no business changing the output of the code, so that's not really a valid concern. This is a standard continuation of Bo's opening move, so if you want to skip to the mid-game, where you explain how you ensure that your tests match your requirements, feel free ;-) – Steve Jessop Jun 14 '12 at 11:35
  • @paperjam: The same logic applies your final `#undef`. What if you forgot to write it ? – ereOn Jun 14 '12 at 11:51
  • A missing `#undef` should give a warning of macro redefinition on most compilers. But forgetting to print one of the variables may easily be missed. I feel we're getting a little off track here. I'm trying to reduce the verbosity of, and duplication in, this code. I know my example solution is incomplete, possibly unworkable and unattractive. Is there another solution perhaps using something like boost tuples together with a minimal PP macro? – paperjam Jun 14 '12 at 11:56
  • @Steve to put comments about unit tests to bed--assume that the code in question is part of my unit-test code. I am trying to reduce code verbosity and repetition in my tests. – paperjam Jun 14 '12 at 11:58
  • @paperjam, regarding C++11, I am curious about your rationale for avoiding said features. In any case, I'm not saying initializer_lists will solve your problem, but you will probably get closer to what you seek. – piwi Jun 14 '12 at 14:37
  • @piwi I guess I have no good reason. Initializer lists solve half the problem and pretty neatly. Replacing each member variable declaration by a macro should make it possible to automate the print function too. I will eventually answer the question myself with this solution if someone doesn't beat me to it. – paperjam Jun 15 '12 at 08:51
  • Might be related: http://stackoverflow.com/q/6181715/430766 – bitmask Jul 29 '12 at 18:04

6 Answers6

208

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.

Paul Fultz II
  • 17,682
  • 13
  • 62
  • 59
  • @Paul: Impressive! One nit => `operator()(T const& t)` to avoid copying, otherwise it's gonna get expensive. – Matthieu M. Aug 01 '12 at 18:05
  • 1
    @Paul: (also, since you are pulling Boost, why not using more of Fusion to start with, like the struct adaptors, etc... ?) – Matthieu M. Aug 01 '12 at 18:23
  • @MatthieuM. First, Boost.Fusion doesn't store the name of the field with the tuple. Secondly, defining or adapting structs can get messy especially when the struct is in a namespace. Also, it makes no difference if a reference is used or not since the `field_data` class is just a reference also. – Paul Fultz II Aug 01 '12 at 19:42
  • @Paul: Ah right, it had not been clear to me that `operator()` would be called with the "field" class instead of the real field. I also agree with Boost.Fusion not giving the name of the field (or at least not directly), though I overcame this using Boost.Fusion.Map: the key type having a static method display its name. It makes access somewhat less convenient (from within the class). – Matthieu M. Aug 02 '12 at 07:22
  • 4
    I had to change the implementation of the TYPEOF macro to BOOST_PP_SEQ_HEAD(x) in order to get this to compile with VS2012. As written it was causing things like make_const – Hippiehunter Jan 05 '13 at 00:14
  • 2
    @Hippiehunter Yes the `TYPEOF` macros require a C99 preprocessor, though there are workarounds to get it to work in msvc. Using `BOOST_PP_SEQ_HEAD` is a simple and effective workaround for msvc, except it won't work the same in C99 preprocessors if the type has commas in it. – Paul Fultz II Jan 05 '13 at 03:02
  • What should I change to make this macro handle an empty struct (i.e. `REFLECTABLE()`) correctly? – AlwaysLearning Dec 10 '15 at 19:57
  • @paul-fultz-ii , can this be made more concise using the features in c++14 or c++17? Do you still use this macro in your code? – Ross Rogers Oct 24 '16 at 22:38
  • 1
    @RossRogers Yes, it can be simplified a little bit and remove the need for boost with C++14, however, there is still a need for macros, as there is no way to get member names as a string. See this talk [here](https://www.youtube.com/watch?v=9QGsEkB4Rfs) that goes over the different techniques. – Paul Fultz II Mar 01 '17 at 20:20
6

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

bytemaster
  • 572
  • 2
  • 5
  • I just want to point out that my solution is a bit more robust and compiles faster than the 'best' solution because I instantiate fewer class types and give the user a member pointer that they can then use for more advanced reflection techniques. Obviously, the Paul's code could be modified, but it is still more verbose. I give him props for providing more pre-processor detail. – bytemaster Aug 02 '12 at 03:36
4

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 :)

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
2

Why do you need to use preprocessor? Introduction to boost.fusion library has an example somewhat similar to your use-case.

zvrba
  • 24,186
  • 3
  • 55
  • 65
2

Here are my 2 cents as an addition to the great REFLECTABLE macro of Paul. I had a need to have an empty list of fields, i.e. REFLECTABLE(), to handle an inheritance hierarchy properly. The following modification handles this case:

// http://stackoverflow.com/a/2831966/2725810
#define REFLECTABLE_0(...)                                                     \
    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 REFLECTABLE_1(...)                                                     \
    static const int fields_n = 0;

#define REFLECTABLE_CONST2(b, ...) REFLECTABLE_##b(__VA_ARGS__)

#define REFLECTABLE_CONST(b, ...) REFLECTABLE_CONST2(b,__VA_ARGS__)


#define REFLECTABLE(...)                                                      \
    REFLECTABLE_CONST(BOOST_PP_IS_EMPTY(__VA_ARGS__), __VA_ARGS__) 
AlwaysLearning
  • 7,257
  • 4
  • 33
  • 68
1

You need a tuple, not a class. This would easily solve all of your problems without having to resort to preprocessor hackery.

Puppy
  • 144,682
  • 38
  • 256
  • 465