Подтвердить что ты не робот

Препроцессор С++: избегать повторения кода списка переменных-членов

У меня есть несколько классов с разными переменными-членами, которые тривиально инициализируются в конструкторе. Вот пример:

struct Person
{
    Person(const char *name, int age)
        :
        name(name),
        age(age)
    {
    }
private:
    const char *name;
    int age;
};

У каждого есть связанная функция print<>().

template <>
void print<Person>(const Person &person)
{
    std::cout << "name=" << name << "\n";
    std::cout << "age=" << age << "\n";
}

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

Например, можно ли использовать технику препроцессора X-args - что-то вроде этого?

#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

Или лучше, шаблонный подход?

Пожалуйста, не задавайте вопросов, почему я хочу это сделать, есть обоснованные проектные решения, которые привели к созданию нескольких похожих объектов с именованными параметрами. По соображениям производительности параметры должны быть названы переменными-членами. Я просто изучаю, можно ли перечислять параметры и их типы только один раз.

4b9b3361

Ответ 1

Что вам нужно сделать, так это то, что препроцессор генерирует данные отражения в полях. Эти данные могут храниться как вложенные классы.

Во-первых, чтобы упростить и очистить его в препроцессоре, мы будем использовать типизированное выражение. Типированное выражение - это просто выражение, которое помещает тип в круглые скобки. Поэтому вместо написания int x вы напишете (int) x. Вот несколько полезных макросов, которые помогут с типизированными выражениями:

#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

Далее мы определяем макрос REFLECTABLE для генерации данных о каждом поле (плюс само поле). Этот макрос будет вызываться вот так:

REFLECTABLE
(
    (const char *) name,
    (int) age
)

Таким образом, используя Boost.PP, мы перебираем каждый аргумент и генерируем такие данные:

// 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)); \
    } \
}; \

Что это такое, генерирует константу fields_n, которая является числом отражаемых полей в классе. Затем он специализируется на field_data для каждого поля. Он также поддерживает класс reflector, поэтому он может обращаться к полям, даже если они являются частными:

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

Теперь, чтобы перебирать поля, мы используем шаблон посетителя. Мы создаем диапазон MPL от 0 до количества полей и получаем доступ к данным поля по этому индексу. Затем он передает данные поля посетителю, которому предоставляется пользователь:

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

Теперь, на минуту истины, мы все вместе. Вот как мы можем определить класс Person:

struct Person
{
    Person(const char *name, int age)
        :
        name(name),
        age(age)
    {
    }
private:
    REFLECTABLE
    (
        (const char *) name,
        (int) age
    )
};

Вот обобщенная функция print_fields:

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

Пример:

int main()
{
    Person p("Tom", 82);
    print_fields(p);
    return 0;
}

Какие выходы:

name=Tom
age=82

И вуаля, мы только что реализовали отражение в С++, в пределах до 100 строк кода.

Ответ 2

Я решил ту же проблему с моей общей структурой, что и код JSON.

Определите макрос: REFLECT (CLASS_NAME, MEMBER_SEQUENCE) где MEMBER_SEQUENCE есть (имя) (возраст) (другое) (...)

У REFLECT развернуть что-то похожее на:

template<>
struct reflector<CLASS_NAME> {
  template<typename Visitor>
  void visit( Visitor&& v ) {
     v( "name" , &CLASS_NAME::name );
     v( "age",   &CLASS_NAME::age  );
     ... 
  }
}

Вы можете использовать BOOST_PP_SEQ_FOREACH, чтобы развернуть SEQ в посетителях.

Затем определите посетителя печати:

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

Ответ 3

Я боюсь, что ваше решение довольно оптимально для этой сокращенной утилиты. Мы можем помочь, если у вас есть дополнительные функции, кроме print, которые выиграют от итерации по полям.

Это прекрасный пример для Boost.Fusion Последовательности Fusion; они могут использоваться для представления времени компиляции. Кроме того, вы можете генерировать более общее поведение во время выполнения.

Итак, вы можете, например, объявить свои элементы, используя Fusion.Map(который ограничивает вас одним вхождением каждого типа) или другими такими фантазиями.

Если ваш тип не соответствует последовательности Fusion (или вы не хотите вмешиваться в его внутренности), в adapted, например BOOST_FUSION_ADAPT_STRUCT. И, конечно же, поскольку не все есть struct (или имеет публичные элементы), также существует более общая версия для классов, она скоро становится некрасивой: BOOST_FUSION_ADAPT_ADT.

Краткий обзор быстрого запуска:

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

Адаптеры позволят вам "адаптировать" тип, поэтому вы получите:

struct Foo { int bar; char const* buzz; };

BOOST_FUSION_ADAPT_STRUCT(
    Foo,
    (int, bar)
    (char const*, buzz)
)

И затем:

int main() {
    Foo foo{1, "Hello");
    for_each(foo, print_xml());
}

Это довольно впечатляющая библиотека:)

Ответ 4

Вам нужен кортеж, а не класс. Это легко разрешит все ваши проблемы, не прибегая к хакерству препроцессоров.

Ответ 5

Зачем вам нужен препроцессор? Введение в библиотеку boost.fusion имеет пример, похожий на ваш прецедент.

Ответ 6

Вот мои 2 цента как дополнение к большому макросу REFLECTABLE Павла. Мне нужно было иметь пустой список полей, т.е. REFLECTABLE(), для правильной обработки иерархии наследования. Следующая модификация обрабатывает этот случай:

// 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__)