Как сделать рефлексию
От: x-code  
Дата: 14.07.15 09:53
Оценка:
А подскажите какие-нибудь решения по рефлексии в С++.
Имеется: много классов (штук 300), они выстроены в иерархию от общего базового класса.
В каждом классе есть поля данных, разных типов:
— базовых (int, float...),
— строки,
— указатели на объекты других классов этой иерархии,
— указатели на некоторые внешние классы.
— массивы базовых типов
— массивы указателей на классы иерархии
— указатели на массивы указателей на классы иерархии
(проект не мой, я просто кое-что допиливаю)
Хочется сделать универсальный обход по объектам этой иерархии классов, как по дереву, с загрузкой данных в GUI дерево.
При этом поля данных должны остаться самими собой, то есть крайне нежелательно заворачивать их в какие-то объектные обертки с перегруженными операторами.

Нужно синтаксически обернуть эти поля данных в какие-то макросы, чтобы в итоге, кроме собственно полей данных, в классах сгенерировалась какая-то функция или метаданные или что-то еще (?)
со следующими свойствами
1. внешняя итерация по множеству полей класса, в общем без разницы какая — последовательная или с произвольным доступом по индексу.
2. через итерацию — обобщенный доступ к каждому элементу, с получением :
2.1 имени самой переменной (это понятно, через # препроцессора),
2.2 текстового значения переменной, если оно есть (для строк, чисел и типов, у которых определен какой-нибудь toString)
2.3 указателя на базовый класс иерархии, если это поле — указатель на любой класс иерархии, иначе null (это для рекурсивного обхода дерева)
3. если объект-массив, то можно его раскрыть рекурсивно как массив

в общем все это желательно совместить с описанием самих полей в классе. Сейчас мне приходится писать отдельные методы, которые возвращают нужную информацию для полей; хотя там все максимально автоматизировано на макросах — это все равно неудобно, можно забыть добавить поле в рефлексию или что-то перепутать.

Проблема именно в том, как в описании класса совместить собственно поля данных и что-то еще; если удастся совместить — остальное дело техники. Должно быть что-то типа такого
struct FOO : BASE
{
//... some code
  REFL_F(int, x)      // field
  REFL_A(float, y, 3) // array 
  REFL_P(BAR*, ptr)   // pointer to class hierarchy object
//... some code
};
Re: Как сделать рефлексию
От: uzhas Ниоткуда  
Дата: 14.07.15 09:55
Оценка:
Здравствуйте, x-code, Вы писали:

XC>А подскажите какие-нибудь решения по рефлексии в С++.

вы уже гуглили?
http://stackoverflow.com/questions/12084781/c-iterate-into-nested-struct-field-with-boost-fusion-adapt-struct
Re: Как сделать рефлексию
От: Went  
Дата: 14.07.15 10:40
Оценка:
Здравствуйте, x-code, Вы писали:

XC>А подскажите какие-нибудь решения по рефлексии в С++.


А вам нужно чтобы рефлексия обозначалась именно в месте определения переменной? Или можно вынести это в отдельную функцию класса или вообще за оный?
Re: Как сделать рефлексию
От: Evgeny.Panasyuk Россия  
Дата: 14.07.15 11:03
Оценка:
Здравствуйте, x-code, Вы писали:

XC>Проблема именно в том, как в описании класса совместить собственно поля данных и что-то еще; если удастся совместить — остальное дело техники. Должно быть что-то типа такого

XC>
struct FOO : BASE
XC>{
XC>//... some code
XC>  REFL_F(int, x)      // field
XC>  REFL_A(float, y, 3) // array 
XC>  REFL_P(BAR*, ptr)   // pointer to class hierarchy object
XC>//... some code
XC>};


Посмотри на Boost.Fusion и на макросы BOOST_FUSION_* (BOOST_FUSION_DEFINE_STRUCT и т.п.). В твоём случае можно сделать аналог таких макросов, тогда код получится вида:
struct FOO : BASE
{
    DEFINE_FIELDS
    (
        FOO, BASE,
        (int, x)
        ((array<float, 3>), y) 
        (BAR*, ptr)
    )
};
Re: Как сделать рефлексию
От: LaptevVV Россия  
Дата: 14.07.15 11:05
Оценка:
А в Qt смотрели? Там вроде сделано...
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[2]: Как сделать рефлексию
От: x-code  
Дата: 14.07.15 11:12
Оценка:
Здравствуйте, Went, Вы писали:

XC>>А подскажите какие-нибудь решения по рефлексии в С++.

W>А вам нужно чтобы рефлексия обозначалась именно в месте определения переменной? Или можно вынести это в отдельную функцию класса или вообще за оный?

В отдельную функцию делается элементарно, я сейчас так и делаю. Хочется именно в месте определения переменной.
Re[3]: Как сделать рефлексию
От: Went  
Дата: 14.07.15 12:49
Оценка:
Здравствуйте, x-code, Вы писали:

XC>В отдельную функцию делается элементарно, я сейчас так и делаю. Хочется именно в месте определения переменной.

Если я правильно понял наши ограничения, то выход один: декларация переменной-члена указанным макросом должна вызывать параллельный код, который зарегистрирует данную переменную в неком хранилище.
Проблемы две:
1. Вытащить имя класса. Если не указать его каким-то вышестоящим макросом, то я даже не знаю как его вытащить иначе
2. Получить код, который будет выполнен для данной регистрации. Просто функцию дописать нельзя. Ее никто не вызовет. Значит нужна переменная, которая в своем конструкторе выполнит регистрацию члена. Если переменная статическая, то это хорошо, потому что код будет выполнен единожды. И переменная должна быть интегральной. Значит, мы можем определить эту переменную каким-то-мусором, а работу выполним в constexpr функции инициализации. И будем молиться, чтобы оптимизатор не выкинул.
struct X
{
  DECL_STRUCT(X);

  DECL_VAR(int, x);
};

развернем во что-то такое:
struct X
{
  typedef X __ThisType;

  int x;
  static const int __x_registrator = register_member_var(&__ThisType::x);
};

Ну, а начинка register_member_var уже вроде как тривиальна.


Правка. Бред. constexpr-функция не имеет сторонних эффектов. Буду думать дальше.
Отредактировано 14.07.2015 13:11 Went . Предыдущая версия .
Re[2]: Как сделать рефлексию
От: Igore Россия  
Дата: 14.07.15 13:09
Оценка: 1 (1) +1
Здравствуйте, LaptevVV, Вы писали:

LVV>А в Qt смотрели? Там вроде сделано...

Чере moc(Meta-Object Compiler), отдельную утилиту которая генерит метоинформацию на основе макросов Q_OBJECT и т.д., с определеными ограничениями.
Re: Как сделать рефлексию
От: Хон Гиль Дон Россия  
Дата: 14.07.15 14:05
Оценка:
Здравствуйте, x-code, Вы писали:

XC>Нужно синтаксически обернуть эти поля данных в какие-то макросы, чтобы в итоге, кроме собственно полей данных, в классах сгенерировалась какая-то функция или метаданные или что-то еще (?)

XC>со следующими свойствами
XC>1. внешняя итерация по множеству полей класса, в общем без разницы какая — последовательная или с произвольным доступом по индексу.
XC>2. через итерацию — обобщенный доступ к каждому элементу, с получением :
XC>2.1 имени самой переменной (это понятно, через # препроцессора),
XC>2.2 текстового значения переменной, если оно есть (для строк, чисел и типов, у которых определен какой-нибудь toString)
XC>2.3 указателя на базовый класс иерархии, если это поле — указатель на любой класс иерархии, иначе null (это для рекурсивного обхода дерева)
XC>3. если объект-массив, то можно его раскрыть рекурсивно как массив

XC>в общем все это желательно совместить с описанием самих полей в классе. Сейчас мне приходится писать отдельные методы, которые возвращают нужную информацию для полей; хотя там все максимально автоматизировано на макросах — это все равно неудобно, можно забыть добавить поле в рефлексию или что-то перепутать.


XC>Проблема именно в том, как в описании класса совместить собственно поля данных и что-то еще; если удастся совместить — остальное дело техники. Должно быть что-то типа такого

XC>
struct FOO : BASE
XC>{
XC>//... some code
XC>  REFL_F(int, x)      // field
XC>  REFL_A(float, y, 3) // array 
XC>  REFL_P(BAR*, ptr)   // pointer to class hierarchy object
XC>//... some code
XC>};


Если хотеть именно определение данных кучей макросов, то сразу вспоминается boost fusion (который выше уже посоветовали). А если посмотреть на список требований, то это сильно похоже на boost serialization — но там разделено определение полей и итерация по ним. А дальше дело вкуса, я обычно предпочитаю serialization.
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Re: Как сделать рефлексию
От: jazzer Россия Skype: enerjazzer
Дата: 14.07.15 15:26
Оценка:
Здравствуйте, x-code, Вы писали:

XC>Нужно синтаксически обернуть эти поля данных в какие-то макросы, чтобы в итоге, кроме собственно полей данных, в классах сгенерировалась какая-то функция или метаданные или что-то еще


У меня такое есть на работе, это несколько килобайт макросов
Зато все супер-настраиваемое и плагинное
Расшарите не могу, сорри.
В качестве направления мысли — список полей представляет собой PP_SEQ, где каждое поле — это тоже PP_SEQ (тип, имя, дальше прочие вкусности, которые распознаются плагинами). Из это можно генерить что угодно, в приципе.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[2]: Как сделать рефлексию
От: x-code  
Дата: 14.07.15 15:54
Оценка:
Здравствуйте, jazzer, Вы писали:

J>У меня такое есть на работе, это несколько килобайт макросов

J>Зато все супер-настраиваемое и плагинное
J>Расшарите не могу, сорри.
J>В качестве направления мысли — список полей представляет собой PP_SEQ, где каждое поле — это тоже PP_SEQ (тип, имя, дальше прочие вкусности, которые распознаются плагинами). Из это можно генерить что угодно, в приципе.

Я делал такое на boost.preprocessor для перечислений. Хотя оказалось, что простые перечисления с метаинформацией (например именами элементов перечисления) дешевле и проще делать не через буст, а очень простым способом: специальными макросами, раскрывающимися или в перечисление, или в описание статического константного массива — в зависимости от макропеременной; и двойным подключением заголовочника с таким перечислением — один раз без макропеременной (использование по умолчанию — для всех мест), другой раз с определенной макропеременной (единственный раз — в специальный .cpp файл).

Можно конечно такое и с классами провернуть... но слишком много придется заворачивать в макросы, и в таких классах нельзя вставлять никакой код без макросов, иначе будет двойное определение. В этом смысле boost.preprocessor лучше, т.к. не требует двойного include.
Re[3]: Как сделать рефлексию
От: jazzer Россия Skype: enerjazzer
Дата: 14.07.15 16:11
Оценка:
Здравствуйте, x-code, Вы писали:

XC>Можно конечно такое и с классами провернуть... но слишком много придется заворачивать в макросы, и в таких классах нельзя вставлять никакой код без макросов, иначе будет двойное определение. В этом смысле boost.preprocessor лучше, т.к. не требует двойного include.


Вот именно boost.preprocessor я и пользуюсь — полет нормальный, код добавляю и сверху, и снизу, и даже наследование поддерживается.
А все потому, что синтаксически это выглядит так:

http://rsdn.ru/forum/cpp/4544930
Автор: jazzer
Дата: 16.12.11
(см. врезку в середине сообщения)

Суть в том, что не структура представляет самой вызов макроса, а макрос зовется внутри структуры. С таким синтаксисом нет проблем добавить что угодно хоть сверху, хоть снизу.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re: Как сделать рефлексию
От: c-smile Канада http://terrainformatica.com
Дата: 14.07.15 16:21
Оценка:
Здравствуйте, x-code, Вы писали:

Решение от Кости Книжника

#include "reflect.h"
#include "typedecl.h"

class A { 
  public:
    int    i;
    char*  pc;
    double d;
  protected:
    long   larr[10];
    A**    ppa;    
    A*     pa;
  public:
    RTTI_DESCRIBE_STRUCT((RTTI_FIELD(i, RTTI_FLD_PUBLIC),
                          RTTI_PTR(pc, RTTI_FLD_PUBLIC),
                          RTTI_FIELD(d, RTTI_FLD_PUBLIC),
                          RTTI_ARRAY(larr, RTTI_FLD_PROTECTED),
                          RTTI_PTR_TO_PTR(ppa, RTTI_FLD_PROTECTED),
                          RTTI_PTR(pa, RTTI_FLD_PROTECTED)));
};


http://www.garret.ru/cppreflection/docs/reflect.html
http://www.garret.ru/reflect-103.zip
Re: Как сделать рефлексию
От: Warturtle  
Дата: 14.07.15 16:35
Оценка:
Здравствуйте, x-code, Вы писали:

XC>А подскажите какие-нибудь решения по рефлексии в С++.

XC>...
Добавлю свои 5 коп. Законченный пример
#include <stdio.h>
#include <iosfwd>
#include <string>
#include <iostream>
#include <typeinfo>

#include "boost/array.hpp"
#include "boost/type_traits.hpp"
#include "boost/mpl/has_xxx.hpp"
#include "boost/preprocessor/library.hpp"
#include "boost/typeof/typeof.hpp"
#include "boost/fusion/support.hpp"
#include "boost/fusion/include/iteration.hpp"
#include "boost/fusion/include/pair.hpp"
#include "boost/fusion/include/map.hpp"
#include "boost/fusion/include/intrinsic.hpp"
#include "boost/fusion/include/functional.hpp"
#include "boost/fusion/include/mpl.hpp"
namespace fusion = boost::fusion;

namespace Utils
{
    //////////////////////////////////////////////////////////////////////////
    template< class TagT, class ValT >
    struct PropertyTraits 
    {
        typedef TagT Tag;
        typedef ValT Value;
        typedef boost::fusion::pair< Tag, Value > Pair;
    };

    //////////////////////////////////////////////////////////////////////////
    struct PropertiesAccessImplBase
    {
        template< class PropT, class SourceT > 
        static typename PropT::Value const & AtVal(SourceT const & from)
        {
            return boost::fusion::at_key< typename PropT::Tag >(from);
        }

        template< class PropT, class SourceT > 
        static typename PropT::Value & AtRef(SourceT & from)
        {
            return boost::fusion::at_key< typename PropT::Tag >(from);
        }
    };

    template< class DerivedT >
    struct PropertiesAccessImpl : PropertiesAccessImplBase
    {
        typedef typename boost::remove_cv< DerivedT >::type DerivedT;

        template< class PropT > 
        typename PropT::Value const & AtVal() const
        {
            return PropertiesAccessImplBase::AtVal< PropT >( static_cast< DerivedT const & >(*this) );
        }
        
        template< class PropT >
        typename PropT::Value & AtRef()
        {
            return PropertiesAccessImplBase::AtRef< PropT >( static_cast< DerivedT & >(*this) );
        }

        template< class PropT > 
        typename PropT::Value const & At() const
        {
            return this->AtVal< PropT >();
        }

        template< class PropT >
        typename PropT::Value & At()
        {
            return this->AtRef< PropT >();
        }

        template< class PropT > 
        typename PropT::Pair AtP() const
        {
            return typename PropT::Pair( this->At< PropT >() );
        }
    };

} //namespace Utils {    


//////////////////////////////////////////////////////////////////////////
/* 
    Упрощенное объявление PropertyTraits, обертки над boost::fusion::pair, 
    которая в месте объявления типа тэга привязывает его к типу значения
*/
#define MY_DEFINE_PROPERTY_TRAITS(TagType, ValType) \
    struct TagType : Utils::PropertyTraits< TagType, ValType > \
    { \
        static char const * Name() { return BOOST_PP_STRINGIZE( TagType ); } \
        static char const * TypeName() { return BOOST_PP_STRINGIZE( ValType ); } \
    } \
    /**/

//    MY_DEFINE_PROPERTY_TRAITS( TagType0, ValueType0 );
//    MY_DEFINE_PROPERTY_TRAITS( TagType1, ValueType1 );
//    ...
#define MY_DEFINE_PROPERTIES_TUPLED_TRAITS(r, data, tag_value) \
    MY_DEFINE_PROPERTY_TRAITS( BOOST_PP_TUPLE_ELEM(2, 0, tag_value), BOOST_PP_TUPLE_ELEM(2, 1, tag_value) ); \
    /**/

//  TagType0::Pair, TagType1::Pair,...
#define MY_DEFINE_PROPERTIES_TYPES_I(r, data, i, tag_value_tuple) \
    BOOST_PP_COMMA_IF(i) BOOST_PP_CAT( BOOST_PP_TUPLE_ELEM(2, 0, tag_value_tuple), ::Pair ) \
    /**/

//    TagType0::Value const & v0, TagType1::Value const & v1,...
#define MY_DEFINE_PROPERTIES_CTOR_PARAMS_I(r, data, i, tag_value_tuple) \
    BOOST_PP_COMMA_IF(i) BOOST_PP_CAT( BOOST_PP_CAT( BOOST_PP_TUPLE_ELEM(2, 0, tag_value_tuple), ::Value const & v ), i ) \
    /**/

//    v0, v1,...
#define MY_DEFINE_PROPERTIES_CAT(z, i, data) \
    BOOST_PP_COMMA_IF(i) BOOST_PP_CAT(data, i) \
    /**/

// MY_DEFINE_PROPERTIES + наличие дефолтного конструктора (0 - нет, 1 - есть) + макрос объявления класса-наследника для Utils::PropertyTraits
#define MY_DEFINE_PROPERTIES_IMPL(TupleName, HasDefaultCtor, HasVirtualDtor, TagDefMacro, TagValueSeq) \
    BOOST_PP_SEQ_FOR_EACH( TagDefMacro, ~, TagValueSeq ) \
    struct TupleName \
        : boost::fusion::map< BOOST_PP_SEQ_FOR_EACH_I( MY_DEFINE_PROPERTIES_TYPES_I, ~, TagValueSeq ) > \
        , public Utils::PropertiesAccessImpl< TupleName > \
    { \
        typedef boost::fusion::map< BOOST_PP_SEQ_FOR_EACH_I( MY_DEFINE_PROPERTIES_TYPES_I, ~, TagValueSeq ) > BaseT; \
        TupleName( BOOST_PP_SEQ_FOR_EACH_I( MY_DEFINE_PROPERTIES_CTOR_PARAMS_I, ~, TagValueSeq ) ) \
            : BaseT( BOOST_PP_REPEAT( BOOST_PP_SEQ_SIZE(TagValueSeq), MY_DEFINE_PROPERTIES_CAT, v ) ) \
        { \
        } \
        BOOST_PP_IF( HasDefaultCtor, TupleName BOOST_PP_LPAREN() BOOST_PP_RPAREN() {}, BOOST_PP_EMPTY() ) \
        BOOST_PP_IF( HasVirtualDtor, BOOST_PP_CAT(~, TupleName) BOOST_PP_LPAREN() BOOST_PP_RPAREN() {}, BOOST_PP_EMPTY() ) \
    } \
    /**/

// MY_DEFINE_PROPERTIES + наличие дефолтного конструктора (0 - нет, 1 - есть)
#define MY_DEFINE_PROPERTIES_EX(TupleName, HasDefaultCtor, TagValueSeq) \
    MY_DEFINE_PROPERTIES_IMPL(TupleName, HasDefaultCtor, 0, MY_DEFINE_PROPERTIES_TUPLED_TRAITS, TagValueSeq) \
    /**/

/* 
    Объявление кортежа с "именованными" полями (на основе boost::fusion::map)
    MY_DEFINE_PROPERTIES( 
        TupleName, 
        ( (TagType0, ValueType0) )
        ( (TagType1, ValueType1) )
        ...
    );
    - разворачивается в совокупность объявлений классов-тэгов и класса на основе boost::fusion::map
    MY_DEFINE_PROPERTY_TRAITS( TagType0, ValueType0 );
    MY_DEFINE_PROPERTY_TRAITS( TagType1, ValueType1 );
    ...

    struct TupleName
        : boost::fusion::map< TagType0::Pair, TagType1::Pair,... >
        , public Utils::PropertiesAccessImpl< TupleName >
    {
        typedef boost::fusion::map< TagType0::Pair, TagType1::Pair,... > BaseT;
        TupleName(TagType0::Value const & v0, TagType1::Value const & v1,...)
            : BaseT(v0, v1,...)
        {
        }
    };
*/
#define MY_DEFINE_PROPERTIES(TupleName, TagValueSeq) \
    MY_DEFINE_PROPERTIES_EX( TupleName, 0, TagValueSeq) \
    /**/

struct Base
{
};

MY_DEFINE_PROPERTIES(
    WeNeed2GoDeeper,
    ( (Zzz, int) )
);

MY_DEFINE_PROPERTIES(
    Bar,
    ( (Aaa, int) )
    ( (Bbb, std::string) )
    ( (Ccc, WeNeed2GoDeeper *) )
);

typedef boost::array< int, 3 > Int3;

struct FooTraits : Base
{
    MY_DEFINE_PROPERTIES_EX(
        Storage, 1,
        ( (x, int) )
        ( (y, Int3) )
        ( (ptr, Bar *) )
    );
};

struct Foo
    : public FooTraits
    , public FooTraits::Storage
{
    Foo()
    {
        this->At< ptr >() = new Bar(12, std::string("qwe"), new WeNeed2GoDeeper(34));
    }

    struct Printer
    {
        Printer(int indent) 
            : m_indent(indent)
        {
        }
        template< class PairT >
        void operator ()(PairT & data) const;

    private:
        int const m_indent;
    };

    void PrintHierarchy()
    {
        fusion::for_each(*this, Printer(0));
    }
};

//////////////////////////////////////////////////////////////////////////
BOOST_MPL_HAS_XXX_TRAIT_DEF( first_type )
BOOST_MPL_HAS_XXX_TRAIT_DEF( second_type )
template< 
    class PairT
    , bool Expand = has_first_type< PairT >::value && has_second_type< PairT >::value && boost::is_pointer< typename PairT::second_type >::value  
>
struct FooPrinterImpl
{
    static void Do(PairT & data, int const indent)
    {
        std::string const prfx(indent, '*');
        ::printf("%s[%s] of '%s'\n", prfx.c_str(), typename PairT::first_type::Name(), typename PairT::first_type::TypeName());
    }
};

template< class PairT >
struct FooPrinterImpl< PairT, true >
{
    static void Do(PairT & data, int const indent)
    {
        if (! data.second)
            return;
        ::printf("----\n");
        fusion::for_each(*data.second, Foo::Printer(indent + 1));
    }
};

template< class PairT >
void Foo::Printer::operator ()(PairT & data) const
{
    FooPrinterImpl< PairT >::Do(data, m_indent);
}

//////////////////////////////////////////////////////////////////////////
int main()
{
    Foo().PrintHierarchy();
}
Re: Как сделать рефлексию
От: antropolog  
Дата: 14.07.15 21:55
Оценка: 1 (1) +7 :)
Здравствуйте, x-code, Вы писали:

возьмите https://pypi.python.org/pypi/CppHeaderParser/ и напишите небольшой скрипт-кодогенератор на питоне. Это будет гораздо более поддерживаемо (при всём уважении к почтенным господам ) чем весь предложенный мрак на препроцессоре и шаблонах.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.