Библиотека compile-time аннотаций
От: TarasKo Голландия  
Дата: 13.01.13 15:27
Оценка: 17 (2)
Сразу пример с несколькими сценариями использования:

struct A
{
// Определяем в структуре 3 поля
// int int_field_;
// std::string string_field_;
// double no_ann_field_;

// Первые два поля имеют аннотации, последнее нет.

    AUX_DECLARE_AND_ANNOTATE(
        ((int, int_field_,
            ((int_annotation, 24.0))
            ((string_annotation, "Privet"))
        ))
        ((std::string, string_field_,
            ((no_serialization, std::true_type()))
            ((no_hash, std::true_type()))
        ))
        ((double, no_ann_field_, BOOST_PP_SEQ_NIL))
      )
};

// Определим метафункцию позволяющую определить есть ли в типе с аннотациями, аннотация no_hash
AUX_DEFINE_MEMBER_DETECTOR(no_hash)

// Визитор для boost::fusion::for_each, см. код ниже
struct dump_members
{
    template<typename MemberType, typename AnnotationsType>
    void operator()(const aux::annotated_member<MemberType, AnnotationsType>& member) const
    {
        cout << "Member value: " << member.value_ << endl;

        // We can make compile time check here whether member has specific annotation
        cout << "\tHas no_hash annotation: " << has_no_hash<AnnotationsType>::value << endl;
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    A a;

    // Use case 1:
    // Явно обращаемся к члену и к типу содержащему его аннотации
    a.int_field_ = 100;
    a.string_field_ = "string field";
    a.no_ann_field_ = 20.;

    A::annotations_for_int_field_ a1;
    A::annotations_for_string_field_ a2;
    A::annotations_for_no_ann_field_ a3;

    cout << "Value of int_annotation for A::int_field_" << a1.int_annotation << endl;    

    // Use case 2:
    // Метафункция проверяющая являются ли поля в типе аннотированными
    cout << "has_annotations<A> = " << aux::has_annotations<A>::value << endl;
    cout << "has_annotations<int> = " << aux::has_annotations<int>::value << endl;

    // Use case 3:
    // Получение членов структуры и связанных с ними аннотаций в виде кортежа, 
    boost::fusion::for_each(a.annotated_tuple(), dump_members());
    
    return 0;
}


А вот реализация
#pragma once

/// @file annotations.hpp

#include <boost/fusion/container/vector.hpp>
#include <boost/fusion/algorithm/iteration/for_each.hpp>
#include <boost/fusion/adapted/struct/define_struct_inline.hpp>

#include <boost/preprocessor/control/if.hpp>
#include <boost/preprocessor/tuple/elem.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/seq/size.hpp>
#include <boost/preprocessor/seq/transform.hpp>
#include <boost/preprocessor/comma_if.hpp>
#include <boost/preprocessor/identity.hpp>
#include <boost/preprocessor/empty.hpp>

#include <boost/mpl/has_xxx.hpp>

namespace aux {

/// Bound together 
template<typename MemberType, typename AnnotationsType>
struct annotated_member
{
    typedef AnnotationsType annotations_type;
    typedef MemberType member_type;

    MemberType& value_;

    annotated_member(MemberType& value) : value_(value) {}
};

//--------------------- USER LEVEL MACROSES --------------------------------------------

/// Declare and annotate member with specified types
/// @code
///struct A
///{
///    AUX_DECLARE_AND_ANNOTATE(
///        ((int, int_field_,
///            ((int_annotation, 24.0))
///            ((string_annotation, "Privet"))
///        ))
///        ((std::string, string_field_,
///            ((no_serialization, std::true_type()))
///            ((no_hash, std::true_type()))
///        ))
///        ((double, no_ann_field_, BOOST_PP_SEQ_NIL))
///      )
//};
/// @endcode
#define AUX_DECLARE_AND_ANNOTATE(X) \
    typedef void annotated_tag; \
    BOOST_PP_SEQ_FOR_EACH_R(1, AUX_DETAIL_DECLARE_MEMBER, _, X) \
    BOOST_PP_SEQ_FOR_EACH_R(1, AUX_DETAIL_DECLARE_ANNOTATION_STRUCT, _, X) \
    AUX_DETAIL_DECLARE_MUTABLE_TUPLE_TYPE(X) \
    AUX_DETAIL_DECLARE_CONST_TUPLE_TYPE(X) 


/// Define metafunction that become true if type member were defined using AUX_DECLARE_AND_ANNOTATE
BOOST_MPL_HAS_XXX_TRAIT_NAMED_DEF(has_annotations, annotated_tag, false);

/// Stolen from http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Member_Detector
/// Define metafunction that detect presence of some member in structure
#define AUX_DEFINE_MEMBER_DETECTOR(X)                                               \
template<typename T> class has_##X {                                                \
    struct Fallback { int X; };                                                     \
    struct Derived : T, Fallback { };                                               \
                                                                                    \
    template<typename U, U> struct Check;                                           \
                                                                                    \
    typedef char ArrayOfOne[1];                                                     \
    typedef char ArrayOfTwo[2];                                                     \
                                                                                    \
    template<typename U> static ArrayOfOne & func(Check<int Fallback::*, &U::X> *); \
    template<typename U> static ArrayOfTwo & func(...);                             \
  public:                                                                           \
    typedef has_##X type;                                                           \
    enum { value = sizeof(func<Derived>(0)) == 2 };                                 \
};

//--------------------- IMPLEMENTATION LEVEL MACROSES --------------------------------------------

// Define type that will be used for tuple. Better to use C++11 template aliasing instead of macro
#define AUX_DETAIL_TUPLE_TYPE boost::fusion::vector

// Accessors for top level tuple
#define AUX_DETAIL_L1_TYPE(tup) BOOST_PP_TUPLE_ELEM(3, 0, tup)
#define AUX_DETAIL_L1_MEMBER(tup) BOOST_PP_TUPLE_ELEM(3, 1, tup)
#define AUX_DETAIL_L1_ANN_SEQ(tup) BOOST_PP_TUPLE_ELEM(3, 2, tup)

// Accessort for annotations level tuple
#define AUX_DETAIL_L2_NAME(tup) BOOST_PP_TUPLE_ELEM(2, 0, tup)
#define AUX_DETAIL_L2_VALUE(tup) BOOST_PP_TUPLE_ELEM(2, 1, tup)

// Produce name for annotation structure
#define AUX_DETAIL_ANN_NAME(tup) BOOST_PP_CAT(annotations_for_, AUX_DETAIL_L1_MEMBER(tup))

// Declare member using top level tuple
#define AUX_DETAIL_DECLARE_MEMBER(r, data, tup) AUX_DETAIL_L1_TYPE(tup) AUX_DETAIL_L1_MEMBER(tup);

#if !defined(AUX_USE_TUPLE_FOR_ANNOTATIONS)

// Produce colon on false and comma on true
#define AUX_DETAIL_COLON_OR_COMMA(cond) BOOST_PP_IF(cond, BOOST_PP_COMMA, BOOST_PP_IDENTITY(:))()

// Produce annotation initializer list for annotation structure constructor
#define AUX_DETAIL_INITIALIZER_LIST(r, data, i, tup) AUX_DETAIL_COLON_OR_COMMA(i) AUX_DETAIL_L2_NAME(tup)(AUX_DETAIL_L2_VALUE(tup))

// Produce declaration of annotation member
#define AUX_DETAIL_ANNOTATIONS_LIST(r, data, i, tup) decltype(AUX_DETAIL_L2_VALUE(tup)) AUX_DETAIL_L2_NAME(tup);

// Declare annotation struct using tuple
#define AUX_DETAIL_DECLARE_ANNOTATION_STRUCT(r, data, tup) \
    struct AUX_DETAIL_ANN_NAME(tup) { \
        AUX_DETAIL_ANN_NAME(tup) () \
            BOOST_PP_SEQ_FOR_EACH_I_R(r, AUX_DETAIL_INITIALIZER_LIST, _, AUX_DETAIL_L1_ANN_SEQ(tup)) {} \
        BOOST_PP_SEQ_FOR_EACH_I_R(r, AUX_DETAIL_ANNOTATIONS_LIST, _, AUX_DETAIL_L1_ANN_SEQ(tup)) \
    };

#else

// TODO: Initialize annotations with provided values

#define AUX_DETAIL_TRANSFORM_TO_FUSION_SEQ(d, data, tup) decltype(AUX_DETAIL_L2_VALUE(tup)), AUX_DETAIL_L2_NAME(tup)

#define AUX_DETAIL_DECLARE_ANNOTATION_STRUCT(r, data, tup) \
    BOOST_FUSION_DEFINE_STRUCT_INLINE(AUX_DETAIL_ANN_NAME(tup), BOOST_PP_SEQ_TRANSFORM(AUX_DETAIL_TRANSFORM_TO_FUSION_SEQ, _, AUX_DETAIL_L1_ANN_SEQ(tup)))

#endif

// Produce ", member" or "member" if i is 0
#define AUX_DETAIL_ENUM_MEMBERS(r, data, i, tup) BOOST_PP_COMMA_IF(i) AUX_DETAIL_L1_MEMBER(tup)

#define AUX_DETAIL_ANNOTATED_MEMBER(r, const_or_empty, i, tup) BOOST_PP_COMMA_IF(i) aux::annotated_member<const_or_empty() AUX_DETAIL_L1_TYPE(tup), AUX_DETAIL_ANN_NAME(tup)>

#define AUX_DETAIL_DECLARE_MUTABLE_TUPLE_TYPE(X) \
    typedef AUX_DETAIL_TUPLE_TYPE<BOOST_PP_SEQ_FOR_EACH_I(AUX_DETAIL_ANNOTATED_MEMBER, BOOST_PP_EMPTY, X)> annotated_tuple_type; \
    annotated_tuple_type annotated_tuple() { return annotated_tuple_type(BOOST_PP_SEQ_FOR_EACH_I(AUX_DETAIL_ENUM_MEMBERS, _, X)); }

#define AUX_DETAIL_DECLARE_CONST_TUPLE_TYPE(X) \
    typedef AUX_DETAIL_TUPLE_TYPE<BOOST_PP_SEQ_FOR_EACH_I(AUX_DETAIL_ANNOTATED_MEMBER, BOOST_PP_IDENTITY(const), X)> const_annotated_tuple_type; \
    const_annotated_tuple_type annotated_tuple() const { return const_annotated_tuple_type(BOOST_PP_SEQ_FOR_EACH_I(AUX_DETAIL_ENUM_MEMBERS, _, X)); }

} // namespace aux
Re: Библиотека compile-time аннотаций
От: Abyx Россия  
Дата: 13.01.13 15:51
Оценка:
Здравствуйте, TarasKo, Вы писали:

интрузивный подход на макросах — зло.
потому что IDE такое не парсит и работать с таким кодом невозможно
In Zen We Trust
Re: Библиотека compile-time аннотаций
От: TarasKo Голландия  
Дата: 13.01.13 16:13
Оценка:
Немного комментариев
Несколько недель назад задавал вопрос про аннотации здесь
Автор: TarasKo
Дата: 28.12.12
, посоветовали cpgf, очень интересная библиотека. Там используется внешняя тулза для генерации кода работы с аннотациями из доксигеновских комментариев, либо можно самому в сторонке создать аннотации для структуры как-то вот так:

    using namespace cpgf;

    GDefineMetaClass<TestObject>
        ::define("annotation::TestObject")
    
        ._annotation("attribute")
            ._element("name", L"annotation::TestObject")
            ._element("cat", mcatClass)
            ._element("dog", TestData(mcatClass, "annotation::TestObject"))

        ._enum<TestObject::WindowStyle>("WindowStyle")
            ._element("ws0", TestObject::ws0)
            ._element("ws1", TestObject::ws1)
            ._element("ws2", TestObject::ws2)
            ._element("ws3", TestObject::ws3)
            ._annotation("attribute")
                ._element("name", L"WindowStyle")
                ._element("cat", mcatEnum)
                ._element("dog", TestData(mcatEnum, "WindowStyle"))

        ._field("width", &TestObject::width)
            ._annotation("attribute")
                ._element("name", L"width")
                ._element("cat", mcatField)
                ._element("dog", TestData(mcatField, "width"))


С такими решениями всегда возникает вопрос, а можно ли сделать тоже самое или хотя бы частично
  • без внешней тулзы
  • с compile-time возможностью определить наличие аннотации у мембера
  • Как наиболее эффективно итерироваться по полям структур и их аннотациям? То есть собственно то, что предлагает Boost.Fusion. Соответственно неплохо бы иметь из коробки представление всех полей с аннотациями в виде кортежа, что бы потом применять алгоритмы из fusion

    Следующий этап — это интегрироваться с std|boost hash и boost.serialization
  • Re[2]: Библиотека compile-time аннотаций
    От: TarasKo Голландия  
    Дата: 13.01.13 16:24
    Оценка:
    Макро конечно зло, но позволяют решить некоторые задачи кодогенерации без привлечения дополнительных утилит
    Что касается IDE, то конкретно здесь Intelli sense у моей домашней MSVC 10 успешно парсит структуру A и показывает все её мемберы и typedef-ы

    Я кстати ещё не полностью структуру макросом дефайню Вот
    тут и вот тут пошли дальше
    Re[2]: Библиотека compile-time аннотаций
    От: enji  
    Дата: 14.01.13 08:40
    Оценка:
    Здравствуйте, Abyx, Вы писали:

    A>Здравствуйте, TarasKo, Вы писали:


    A>интрузивный подход на макросах — зло.

    A>потому что IDE такое не парсит и работать с таким кодом невозможно

    конкретно эту либу не пробовал, но eclipse+cdt с буст-препроцессором справляется относительно нормально. Автокомплит работает, с рефакторингом правда похуже.
    Re: Библиотека compile-time аннотаций
    От: jyuyjiyuijyu  
    Дата: 15.01.13 21:50
    Оценка:
    Здравствуйте, TarasKo, Вы писали:

    а что означает префикс AUX ?
    Re[2]: Библиотека compile-time аннотаций
    От: Abyx Россия  
    Дата: 15.01.13 22:01
    Оценка: -1
    Здравствуйте, jyuyjiyuijyu, Вы писали:

    J>а что означает префикс AUX ?

    https://www.google.ru/search?q=aux
    In Zen We Trust
    Re: Библиотека compile-time аннотаций
    От: TarasKo Голландия  
    Дата: 03.02.13 16:08
    Оценка: 6 (2)
    Освоил бустовый квикбук, написал простой туториал, выложил на gh-pages. Ещё добавил поддержку boost.hash и boost.serialize. Ну и ещё дал имя библиотэке
    сppan
    Доки, простите за мой английский
    Re[2]: Библиотека compile-time аннотаций
    От: niXman Ниоткуда https://github.com/niXman
    Дата: 08.02.13 08:17
    Оценка:
    Здравствуйте, TarasKo, Вы писали:

    TK>Следующий этап — это интегрироваться с std|boost hash и boost.serialization

    возможно будет интересно, потыкать альтернативу boost.serialization — YAS
    Автор: niXman
    Дата: 13.10.12
    пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
    Re[2]: Библиотека compile-time аннотаций
    От: jazzer Россия Skype: enerjazzer
    Дата: 08.02.13 08:23
    Оценка: 1 (1)
    Здравствуйте, jyuyjiyuijyu, Вы писали:

    J>Здравствуйте, TarasKo, Вы писали:


    J>а что означает префикс AUX ?


    auxiliary — "вспомогательный"
    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[3]: Библиотека compile-time аннотаций
    От: TarasKo Голландия  
    Дата: 15.02.13 23:24
    Оценка:
    Захотел добавить поддержку yas в cppan но столкнулся с такими проблемами.

    — Самый прямой путь это переопределить свободную функцию serialize, но тут как и с Boost.Serialization большая проблема. Мне нужно её переопределить для любого типа удовлетворяющего метафункции cppan::has_annotations. То есть что-то типа

    template<typename Archive, typename T>
    void serialize(Archive& archive, T& value, boost::enable_if< ::cppan::has_annotations<T> >::type* = 0)


    Но это не скомпилячится, потому что имеет такой же приоритет при разрешении перегрузки как и глобальный yas::serialize

    — Другой путь: поскольку cppan типы являются fusion последовательностями, то логично взять поддержку fusion-а которая есть в yas, определить serialize для cppan::member, и в принципе должно работать.
    Беда в том, что поддерживаются только те контейнеры которые есть во fusion-е. Хотя по идее можно было бы взять boost::enable_if<boost::fusion::is_sequence<T>>, и написать общий код для любых последовательностей. Кстати поддерживаются ли fusion view-хи, типа transform_view? Я плохо знаю тонкости сериализации, тут наверно есть какие-то свои подводные камни.
    Re[4]: Библиотека compile-time аннотаций
    От: niXman Ниоткуда https://github.com/niXman
    Дата: 16.02.13 05:11
    Оценка:
    Здравствуйте, TarasKo, Вы писали:

    TK>- Самый прямой путь это переопределить свободную функцию serialize, но тут как и с Boost.Serialization большая проблема. Мне нужно её переопределить для любого типа удовлетворяющего метафункции cppan::has_annotations. То есть что-то типа


    TK>
    TK>template<typename Archive, typename T>
    TK>void serialize(Archive& archive, T& value, boost::enable_if< ::cppan::has_annotations<T> >::type* = 0)
    TK>


    TK>Но это не скомпилячится, потому что имеет такой же приоритет при разрешении перегрузки как и глобальный yas::serialize

    эм.. если мной маразм еще не полностью завладел, то yas не использует глобальные функции serialize().
    сериализаторы — это специализированные шаблоны serializer<T> с методом apply(Archive, T).
    типа:
    template<typename T>
    struct serializer<
        type_prop::is_pod,
        ser_method::use_internal_serializer,
        archive_type::binary,
        direction::out,
        T
    > {
        template<typename Archive>
        static Archive& apply(Archive& ar, const T& v) {
            ar.write(reinterpret_cast<const char*>(&v), sizeof(T));
            return ar;
        }
    };

    так что, объясни как-то доходчивей, что ли...
    кстати, yas доступен и на LWS:
    http://liveworkspace.org/code/3BOUco$0

    TK>- Другой путь: поскольку cppan типы являются fusion последовательностями, то логично взять поддержку fusion-а которая есть в yas, определить serialize для cppan::member, и в принципе должно работать.

    TK>Беда в том, что поддерживаются только те контейнеры которые есть во fusion-е.
    да, тут можно подумать над предоставлением сериализации не для fusion-конкретных типов, а для fusion-концептов...
    пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
    Re[5]: Библиотека compile-time аннотаций
    От: niXman Ниоткуда https://github.com/niXman
    Дата: 16.02.13 05:21
    Оценка:
    Здравствуйте, niXman, Вы писали:

    X>fusion-конкретных типов, а для fusion-концептов...

    конкретных fusion-типов, а для fusion-концептов...
    пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
    Re[5]: Библиотека compile-time аннотаций
    От: TarasKo Голландия  
    Дата: 16.02.13 08:48
    Оценка:
    Да, я просто полез в примеры (те что в yas/example), а там нет нигде упоминания о struct serializer. Кроме примеров я больше никуда не смотрел.
    Те не менее с этой структурой та же проблема, её не специализировать для множества сущностей, не хватает параметра typename Enable = void.
    Re[6]: Библиотека compile-time аннотаций
    От: niXman Ниоткуда https://github.com/niXman
    Дата: 16.02.13 16:47
    Оценка:
    Здравствуйте, TarasKo, Вы писали:

    TK>Да, я просто полез в примеры (те что в yas/example), а там нет нигде упоминания о struct serializer. Кроме примеров я больше никуда не смотрел.

    там еще есть примеры, когда функция serialize() является мембером сериализуемого типа. (по аналогии с boost.serialization)

    TK>Те не менее с этой структурой та же проблема, её не специализировать для множества сущностей, не хватает параметра typename Enable = void.

    эта структура — приватная реализация. пользователю yas вообще не нужно о ней знать.


    признаюсь, я все еще не понял сути проблемы %)
    пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
    Re[7]: Библиотека compile-time аннотаций
    От: TarasKo Голландия  
    Дата: 17.02.13 20:24
    Оценка:
    X>признаюсь, я все еще не понял сути проблемы %)

    Я не смог понять, как в yas неинтрузивно написать serialize не для одного класса, а сразу для всех классов, которые удовлетворяют некой метафункции.

    Другими словами.
    Хочу сериализовывать все свои классы. Есть библиотека yas, которая говорит, что, что бы сериализовать ваш класс, вам надо либо добавить в него метод serialize, либо написать свободную функцию serialize в своем namespace-e и через ADL она будет найдена и запользована yas-ом. Примеры первого и второго подхода лежат в yas/examples

    У всех моих классов есть обобщённый механизм, позволяющий проитерироваться по всех их членам. Я не хочу в каждый свой класс добавлять функцию serialize. Я хочу написать одну свободную функцию serialize, которая бы использовалась для всех моих классов, через обобщённый механизм итерировалась по членам моих классов и передавала бы их в архив. При этом "Мой" или "не мой" класс, определяет метафункция cppan::has_annotations<T>.

    Как это сделать?

    Если бы это можно было сделать, это была бы мегафича, которой кстати нет в Boost.Serialize
    Re[8]: Библиотека compile-time аннотаций
    От: niXman Ниоткуда https://github.com/niXman
    Дата: 18.02.13 06:44
    Оценка:
    Здравствуйте, TarasKo, Вы писали:

    TK>Я не смог понять, как в yas неинтрузивно написать serialize не для одного класса, а сразу для всех классов, которые удовлетворяют некой метафункции.

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

    TK>Как это сделать?

    пока никак. подумаю над этим. возможно, увижу смысл рефакнуть проект...
    пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
     
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.