Препроцессор 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

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

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

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 строках кода.

Я решил ту же проблему с моей общей структурой в коде 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

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

Это прекрасный пример для последовательностей Boost.Fusion 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());
}

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

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

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

// http://stackru.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__) 

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

Другие вопросы по тегам