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

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

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

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

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

 Rook14 июн. 2012 г., 12:49
Говоря как кто-то, кто проделал большую часть обслуживания в C ++-приложении с большим количеством макросов ... пожалуйста, пожалуйста, пожалуйста, не пишите свой собственный язык, используя макросы. Они - боль, с которой приходится работать, боль, которую я испытываю на рефакторинг, и боль, которую должны понимать новые разработчики. Вы не пишете C ++ в своем примере, вы пишете свой собственный проприетарный DSL. Конечно, это скорее эстетическая / управленческая точка зрения, чем чисто техническая.
 piwi14 июн. 2012 г., 12:23
Можете ли вы использовать функции C ++ 11? Потому что вы можете посмотреть списки инициализаторов и / или унифицированную инициализацию; они могут помочь вам решить эту проблему (см .:en.wikipedia.org/wiki/…)
 Rook14 июн. 2012 г., 12:54
Я чувствую, что вам будет лучше, если вы будете писать спецификацию класса относительно простым декларативным способом (возможно, XML), а затем использовать сценарий для генерации шаблона для вас. Это дает преимущество использования двух стандартных языков (C ++ и XML), и потеря, удаление или замена стороны XML / скрипта не должны делать C ++ бесполезным или непонятным. Возможно, это решение слишком тяжеловесно для вас, но ваше будущее само поблагодарит меня, когда через пару лет придется внести в него изменения ;-)
 paperjam14 июн. 2012 г., 12:28
@AndersK theconst char * только ради примера
 paperjam14 июн. 2012 г., 12:51
@Rook Я разделяю ваши опасения, а также избегаю макросов, где это возможно. Я не против DSL, так как он подслащивает синтаксис моегоPerson-подобные классы и цель здесь состоит в том, чтобы удалить подверженное ошибкам повторение одной и той же информации.

Ответы на вопрос(6)

пример, несколько похожий на ваш вариант использования.

Решение Вопроса

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

Во-первых, чтобы было проще и понятнее записать его в препроцессоре, мы будем использовать типизированное выражение. Типизированное выражение - это просто выражение, которое помещает тип в круглые скобки. Так что вместо того, чтобы писать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

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

 05 янв. 2013 г., 01:14
Мне пришлось изменить реализацию макроса TYPEOF на BOOST_PP_SEQ_HEAD (x), чтобы заставить его компилироваться с VS2012. Как написано, это вызывало такие вещи, как make_const & lt; const char *, name, & gt;
 01 авг. 2012 г., 20:23
@Paul: (также, поскольку вы используете Boost, почему бы не использовать больше Fusion для начала, например, адаптеры struct и т. Д.??)
 05 янв. 2013 г., 04:02
@Hippiehunter Да,TYPEOF Для макросов требуется препроцессор C99, хотя есть обходные пути, чтобы заставить его работать в msvc. С помощьюBOOST_PP_SEQ_HEAD это простой и эффективный обходной путь для msvc, за исключением того, что он не будет работать так же в препроцессорах C99, если тип содержит запятые.
 31 июл. 2012 г., 23:06
Это должно получить награду за лучшие ответы на stackoverflow! Очень впечатляющий макро вуду здесь.
 01 авг. 2012 г., 01:48
+1: это ужасно и страшно. Мне это нравится!

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

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

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

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

Если ваш тип не соответствует Fusion Sequence (или вы не хотите вмешиваться в его внутренности), в адаптере есть адаптеры.адаптированный раздел, такой как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());
}

Это довольно внушительная библиотека :)

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

 01 авг. 2012 г., 03:25
Кортежи не названы, поэтому обращаться к ним некрасиво, и вы не можете сериализовать имя поля с ними (так как имя отсутствует).
 16 мар. 2014 г., 01:41
@Paul а как насчет Fusion :: Map тогда?

Определить макрос: 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

 02 авг. 2012 г., 05:36
Я просто хочу отметить, что мое решение немного более надежно и компилируется быстрее, чем «лучшие». решение, потому что я создаю меньше типов классов и даю пользователю указатель на член, который он затем может использовать для более продвинутых методов отражения. Очевидно, что код Пола может быть изменен, но он все еще более многословен. Я даю ему реквизит для обеспечения большей детализации препроцессора.

Ваш ответ на вопрос