C++ macro/metaprogram to determine number of members at compile time
Asked Answered
R

6

7

I am working on an application with a message based / asynchronous agent-like architecture. There will be a few dozen distinct message types, each represented by C++ types.

class message_a
{
  long long identifier;
  double some_value;
  class something_else;
  ...//many more data members
}

Is it possible to write a macro/meta-program that would allow calculating the number of data members within the class at compile time?

//eg:

class message_b
{
  long long identifier;
  char foobar;
}


bitset<message_b::count_members> thebits;

I am not familiar with C++ meta programming, but could boost::mpl::vector allow me to accomplish this type of calculation?

Rebuke answered 27/7, 2011 at 12:52 Comment(5)
Why not just use std::tuple<double, something_else> data; for your data member, and std::tuple_size<message_a::data>::value as the count?Philibeg
I would like to access the members by name in the calling code, rather than position.Rebuke
You could always wrap the tuple members into accessors, like double & some_value() { return std::get<0>(data); } etc...Philibeg
Thanks all for your suggestions, I have not decided on a path forward yet. But will follow up on your suggestions.Rebuke
read my answer here. Also, here's the link to my repo. pod_reflection lib (only stl, no boost involved) can be used to get maximum number of classes in a pod data-type and get tuple of typesRaving
P
3

No, there is no way in C++ to know the names of all members or how many members are actually there.

You could store all types in a mpl::vector along in your classes but then you face the problem of how to turn them into members with appropriate names (which you cannot achieve without some macro hackery).

Using std::tuple instead of PODs is a solution that generally works but makes for incredible messy code when you actually work with the tuple (no named variables) unless you convert it at some point or have a wrapper that forwards accessors onto the tuple member.

class message {
public:
  // ctors
  const int& foo() const { return std::get<0>(data); }
  // continue boiler plate with const overloads etc

  static std::size_t nun_members() { return std::tuple_size<data>::value; }
private:
  std::tuple<int, long long, foo> data;
};

A solution with Boost.PP and MPL:

#include <boost/mpl/vector.hpp>
#include <boost/mpl/at.hpp>
#include <boost/preprocessor.hpp>
#include <boost/preprocessor/arithmetic/inc.hpp>

struct Foo {
  typedef boost::mpl::vector<int, double, long long> types;

// corresponding type names here
#define SEQ (foo)(bar)(baz)
#define MACRO(r, data, i, elem) boost::mpl::at< types, boost::mpl::int_<i> >::type elem;
BOOST_PP_SEQ_FOR_EACH_I(MACRO, 0, SEQ)

};

int main() {
  Foo a;
  a.foo;
}

I didn't test it so there could be bugs.

Petrography answered 27/7, 2011 at 13:3 Comment(2)
Some macro hackery is not off the table. Effectively I want to do what std::tuple_size<> is doing. But I would like to avoid all the boilerplate of accessor functions.Rebuke
I'll fiddle something together with BOOST_PP. Also you should have a look at Boost.Fusion, especially ADAPT_STRUCT. Although that way you get more than you bargain for.Petrography
P
4

as others already suggested, you need Boost.Fusion and its BOOST_FUSION_DEFINE_STRUCT. You'll need to define your struct once using unused but simple syntax. As result you receive required count_members (usually named as size) and much more flexibility than just that.

Your examples:

Definition:

BOOST_FUSION_DEFINE_STRUCT(
    (), message_a,
    (long long, identifier),
    (double, some_value)
)

usage:

message_a a;
size_t count_members = message_a::size;
Peewee answered 27/7, 2011 at 21:56 Comment(0)
P
3

No, there is no way in C++ to know the names of all members or how many members are actually there.

You could store all types in a mpl::vector along in your classes but then you face the problem of how to turn them into members with appropriate names (which you cannot achieve without some macro hackery).

Using std::tuple instead of PODs is a solution that generally works but makes for incredible messy code when you actually work with the tuple (no named variables) unless you convert it at some point or have a wrapper that forwards accessors onto the tuple member.

class message {
public:
  // ctors
  const int& foo() const { return std::get<0>(data); }
  // continue boiler plate with const overloads etc

  static std::size_t nun_members() { return std::tuple_size<data>::value; }
private:
  std::tuple<int, long long, foo> data;
};

A solution with Boost.PP and MPL:

#include <boost/mpl/vector.hpp>
#include <boost/mpl/at.hpp>
#include <boost/preprocessor.hpp>
#include <boost/preprocessor/arithmetic/inc.hpp>

struct Foo {
  typedef boost::mpl::vector<int, double, long long> types;

// corresponding type names here
#define SEQ (foo)(bar)(baz)
#define MACRO(r, data, i, elem) boost::mpl::at< types, boost::mpl::int_<i> >::type elem;
BOOST_PP_SEQ_FOR_EACH_I(MACRO, 0, SEQ)

};

int main() {
  Foo a;
  a.foo;
}

I didn't test it so there could be bugs.

Petrography answered 27/7, 2011 at 13:3 Comment(2)
Some macro hackery is not off the table. Effectively I want to do what std::tuple_size<> is doing. But I would like to avoid all the boilerplate of accessor functions.Rebuke
I'll fiddle something together with BOOST_PP. Also you should have a look at Boost.Fusion, especially ADAPT_STRUCT. Although that way you get more than you bargain for.Petrography
A
2

There are several answers simply saying that it is not possible, and if you hadn't linked to magic_get I would've agreed with them. But magic_get shows, to my amazement, that it actually is possible in some cases. This goes to show that proving that something is not possible is harder than proving that something is possible!

The short answer to your question would be to use the facilities in magic_get directly rather than reimplement them yourself. After all, even looking at the pre-Boost version of the code, it's not exactly clear how it works. At one point in the comments it mentions something about constructor arguments; I suspect this is the key, because it is possible to count the arguments to a regular function, so perhaps it is counting the number of arguments needed to brace-initialise the struct. This indicates that it may only be possible with plain old structs rather than objects with your own methods.

Despite all this, I would suggest using a reflection library as others have suggested. A good one that I often recommend is Google's protobuf library, which has reflection and serialisation along with multi-language support. However, it is intended only for data-only objects (like plain old structs but with vectors and strings).

Aneroid answered 22/8, 2017 at 15:3 Comment(0)
E
0

Plain structs do not support counting members, but boost::fusion offers a good way to declare a struct that is count- and iteratable.

Ere answered 27/7, 2011 at 13:12 Comment(3)
Most likely, boost does it trough the multiple inheritance. If so, that's not the same.Coheir
Well, it provides adapter macros for plain structs as well, so I assume it can work on plain structs (probably MPL-building a list of types on top of it) and does so. Alas, no way to know but reading and asking :-).Ere
Yeah, it is sort of possible to get offset of member fields, then you can build a compile-time list of offsets. Wrapping that with some crazy macros possibly can do the magic :)Coheir
M
0

Something like this might get you closer:

struct Foo {
    Foo() : a(boost::get<0>(values)), b(boost::get<1>(values)) {}
    int &a;
    float &b;
    typedef boost::tuple<int,float> values_t;
    values_t values;
};
Mustard answered 27/7, 2011 at 17:46 Comment(0)
A
0

If your types respect some properties ("SimpleAggregate"), you might use magic_get (which is now boost_pfr) (from C++14/C++17).

So you will have something like:

class message_b
{
public;
  long long identifier;
  char foobar;
};

static_assert(boost::pfr::tuple_size<message_b>::value == 2);
Archducal answered 19/5, 2022 at 8:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.