Макрос / метапрограмма C++ для определения количества членов во время компиляции

Я работаю над приложением с основанной на сообщениях / асинхронной агентоподобной архитектурой. Будет несколько десятков различных типов сообщений, каждый из которых представлен типами C++.

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

Можно ли написать макрос / метапрограмму, которая позволила бы вычислить количество членов данных в классе во время компиляции?

//например:

class message_b
{
  long long identifier;
  char foobar;
}


bitset<message_b::count_members> thebits;

Я не знаком с метапрограммированием на C++, но может ли boost::mpl::vector позволить мне выполнить этот тип вычислений?

6 ответов

Решение

Нет, в C++ нет способа узнать имена всех членов или сколько членов на самом деле там.

Вы можете хранить все типы в mpl::vector в ваших классах, но затем вы сталкиваетесь с проблемой того, как превратить их в членов с соответствующими именами (чего вы не можете достичь без некоторой макропрограммы).

С помощью std::tuple вместо POD - это решение, которое обычно работает, но создает невероятно запутанный код, когда вы фактически работаете с кортежем (без именованных переменных), если вы не конвертируете его в какой-то момент или не имеете обертку, которая перенаправляет методы доступа на член кортежа.

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;
};

Решение с Boost.PP и 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;
}

Я не проверял, чтобы не было ошибок.

Как уже предлагали другие, вам нужен Boost.Fusion и его BOOST_FUSION_DEFINE_STRUCT. Вы должны будете определить свою структуру один раз, используя неиспользуемый, но простой синтаксис. В результате вы получите необходимые count_members (обычно называется size) и гораздо больше гибкости, чем просто это.

Ваши примеры:

Определение:

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

использование:

message_a a;
size_t count_members = message_a::size;

Есть несколько ответов, в которых просто говорится, что это невозможно, и если бы вы не связались с magic_get, я бы с ними согласился. Но magic_get показывает, к моему изумлению, что в некоторых случаях это действительно возможно. Это говорит о том, что доказать, что что-то невозможно, сложнее, чем доказать, что что-то возможно!

Короткий ответ на ваш вопрос - использовать средства в magic_get напрямую, а не переопределять их самостоятельно. В конце концов, даже если взглянуть на предварительную версию кода, не совсем понятно, как она работает. В одном месте в комментариях упоминается кое-что об аргументах конструктора; Я подозреваю, что это ключ, потому что можно посчитать аргументы обычной функции, поэтому, возможно, он подсчитывает количество аргументов, необходимое для инициализации структуры скобками. Это указывает на то, что это возможно только с простыми старыми структурами, а не объектами с вашими собственными методами.

Несмотря на все это, я бы предложил использовать библиотеку отражений, как предлагали другие. Хорошая библиотека, которую я часто рекомендую, это библиотека protobuf от Google, в которой есть рефлексия и сериализация, а также многоязычная поддержка. Однако он предназначен только для объектов, предназначенных только для данных (например, простые старые структуры, но с векторами и строками).

Нечто подобное может приблизить вас

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;
};

Простые структуры не поддерживают подсчет членов, но boost::fusion предлагает хороший способ объявить структуру, которая является итеративной.

Если ваши типы учитывают некоторые свойства ( «SimpleAggregate» ), вы можете использовать magic_get (теперь это boost_pfr ) (из C++14/C++17).

Итак, у вас будет что-то вроде:

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

static_assert(boost::pfr::tuple_size<message_b>::value == 2);
Другие вопросы по тегам