Imagine we have some sort of protocol with hundreds of message types, each of which we want to model by a C++ class. Since each class should be able to process each field automatically, a natural solution is to just have an std::tuple with all the required types:
std::tuple<int, double, char> message;
print(message);   // the usual variadic magic
This is all fine and well. However, now I want to give each field a name, and I want to be able to use the name when referring to the field in my code, as well as get a textual representation of it. Naively, or in C, I might have written:
struct Message
{
    int    header;
    double temperature;
    char   flag;
};
That way we lose the recursive automagic processing power of the tuple, but we can name each field literally. In C++, we can do both by means of an enum:
struct Message
{
    enum FieldID { header, temperature, flag };
    static const char * FieldNames[] = { "header", "temperature", "flag" };
    typedef std::tuple<int, double, char> tuple_type;
    template <FieldID I>
    typename std::tuple_element<I, tuple_type>::type & get()
    { return std::get<I>(data); }
    template <FieldID I>
    static const char * name() { return FieldNames[I]; }
    tuple_type data;
};
Now I can say, Message m; m.get<Message::header>() = 12; etc., and I can recurse over the fields and make each print out their own value prefixed by their own name, etc.
Now the question: How can I author such code efficiently, without repetition?
Ideally, I want to be able to say this:
START_MESSAGE(Message)
ADDFIELD(int, header)
ADDFIELD(double, temperature)
ADDFIELD(char, flag)
END_MESSAGE
Is there any way, combining preprocessor, Boost and C++11, to achieve something like this without the need for external generation tools? (I think Boost.Preprocessor calls this "horizontal" and "vertical" repetition. I need to "transpose" the field data somehow.) The key feature here is that I never have to repeat any of the information, and that modifying or adding one field only requires one single change.
You can do this with boost's preprocessor sequences.
#define CREATE_MESSAGE(NAME, SEQ) ...
CREATE_MESSAGE(SomeMessage,
  (int)(header)
  (double)(temperature)
  (char)(flag)
)
You would need to iterate over each pair to generate the definitions. I don't have any example code handy, though I can probably arrange some if it is interesting.
At one point I had a generator for something like this that also generated all the serialization for the fields. I kind of felt like it went a little too far. I feel like concrete definitions and declarative visitors on the fields is more straight forward. It's a little less magical in case someone else had to maintain the code after me. I don't know you're situation obviously, just after implementing it I still had reservations. :)
It would be cool to look at again with the C++11 features, though I haven't had a chance.
Update:
There are still a few kinks to work out, but this is mostly working.
#include <boost/preprocessor.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/arithmetic/mod.hpp>
#include <boost/preprocessor/control/if.hpp>
#include <tuple>
#define PRIV_CR_FIELDS(r, data, i, elem) \
    BOOST_PP_IF(BOOST_PP_MOD(i, 2),elem BOOST_PP_COMMA,BOOST_PP_EMPTY)()
#define PRIV_CR_STRINGS(r, data, i, elem) \
    BOOST_PP_IF(BOOST_PP_MOD(i, 2),BOOST_PP_STRINGIZE(elem) BOOST_PP_COMMA,BOOST_P
#define PRIV_CR_TYPES(r, data, i, elem) \
    BOOST_PP_IF(BOOST_PP_MOD(i, 2),BOOST_PP_EMPTY,elem BOOST_PP_COMMA)()
#define CREATE_MESSAGE(NAME, SEQ) \
    struct NAME { \
        enum FieldID { \
            BOOST_PP_SEQ_FOR_EACH_I(PRIV_CR_FIELDS, _, SEQ) \
        }; \
        std::tuple< \
            BOOST_PP_SEQ_FOR_EACH_I(PRIV_CR_TYPES, _, SEQ) \
        > data;\
        template <FieldID I> \
            auto get() -> decltype(std::get<I>(data)) { \
                return std::get<I>(data); \
            } \
        template <FieldID I> \
            static const char * name() { \
                static constexpr char *FieldNames[] = { \
                    BOOST_PP_SEQ_FOR_EACH_I(PRIV_CR_STRINGS, _, SEQ) \
                }; \
                return FieldNames[I]; \
            } \
    };
CREATE_MESSAGE(foo,
        (int)(a)
        (float)(b)
    )
#undef CREATE_MESSAGE
int main(int argc, char ** argv) {
    foo f;
    f.get<foo::a>() = 12;
    return 0;
}
It is having problems with get's decltype. I haven't really used tuple to know what to expect there. I don't think it has anything to do with how you generate the types or fields, though.
Here is what the preprocessor is producing with -E:
struct foo { 
  enum FieldID { a , b , }; 
  std::tuple< int , float , > data;
  template <FieldID I> 
    auto get() -> decltype(std::get<I>(data)) { 
      return std::get<I>(data); 
  } 
  template <FieldID I> static const char * name() { 
    static constexpr char *FieldNames[] = { "a" , "b" , }; 
    return FieldNames[I]; 
  } 
};
                        This isn't an answer, but merely another (scary) idea to consider.  I have a inl file I wrote once that kinda sorta is vaguely similar.  It's here: http://ideone.com/6CvgR
The basic concept is the caller does this:
#define BITNAME color
#define BITTYPES SEPERATOR(Red) SEPERATOR(Green) SEPERATOR(Blue)
#define BITTYPE unsigned char
#include "BitField.inl"
and the inl file creates a custom bitfield type with named members by redefining SEPERATOR and then using BITTYPES again.  Which can then be used easily, including a ToString function.
 colorBitfield Pixel;
 Pixel.BitField = 0; // sets all values to zero;
 Pixel.Green = 1; // activates green;
 std::cout << "Pixel.Bitfield=" << (int)Pixel.BitField << std::endl;  //this is machine dependant, probably 2 (010).
 Pixel.BitField |= (colorBitfield::GreenFlag | colorBitfield::BlueFlag); // enables Green and Blue
 std::cout << "BlueFlag=" << (Pixel.BitField & colorBitfield::BlueFlag) << std::endl; // 1, true.
 std::cout << "sizeof(colorBitField)=" << sizeof(colorBitfield) << std::endl;
The inline file itself is terrifying code, but some approach vaguely like this might simplify the caller's usage.
If I have time later, I'll see if I can make something along this idea for what you're wanting.
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