Макрос / метапрограмма 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);