Сразу пример с несколькими сценариями использования:
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
, посоветовали cpgf, очень интересная библиотека. Там используется внешняя тулза для генерации кода работы с аннотациями из доксигеновских комментариев, либо можно самому в сторонке создать аннотации для структуры как-то вот так:
С такими решениями всегда возникает вопрос, а можно ли сделать тоже самое или хотя бы частично
без внешней тулзы
с compile-time возможностью определить наличие аннотации у мембера
Как наиболее эффективно итерироваться по полям структур и их аннотациям? То есть собственно то, что предлагает Boost.Fusion. Соответственно неплохо бы иметь из коробки представление всех полей с аннотациями в виде кортежа, что бы потом применять алгоритмы из fusion
Следующий этап — это интегрироваться с std|boost hash и boost.serialization
Макро конечно зло, но позволяют решить некоторые задачи кодогенерации без привлечения дополнительных утилит
Что касается IDE, то конкретно здесь Intelli sense у моей домашней MSVC 10 успешно парсит структуру A и показывает все её мемберы и typedef-ы
Я кстати ещё не полностью структуру макросом дефайню Вот тут и вот тут пошли дальше
Здравствуйте, Abyx, Вы писали:
A>Здравствуйте, TarasKo, Вы писали:
A>интрузивный подход на макросах — зло. A>потому что IDE такое не парсит и работать с таким кодом невозможно
конкретно эту либу не пробовал, но eclipse+cdt с буст-препроцессором справляется относительно нормально. Автокомплит работает, с рефакторингом правда похуже.
Освоил бустовый квикбук, написал простой туториал, выложил на gh-pages. Ещё добавил поддержку boost.hash и boost.serialize. Ну и ещё дал имя библиотэке сppan Доки, простите за мой английский
Здравствуйте, TarasKo, Вы писали:
TK>Следующий этап — это интегрироваться с std|boost hash и boost.serialization
возможно будет интересно, потыкать альтернативу boost.serialization — YAS
Захотел добавить поддержку yas в cppan но столкнулся с такими проблемами.
— Самый прямой путь это переопределить свободную функцию serialize, но тут как и с Boost.Serialization большая проблема. Мне нужно её переопределить для любого типа удовлетворяющего метафункции cppan::has_annotations. То есть что-то типа
Но это не скомпилячится, потому что имеет такой же приоритет при разрешении перегрузки как и глобальный yas::serialize
— Другой путь: поскольку cppan типы являются fusion последовательностями, то логично взять поддержку fusion-а которая есть в yas, определить serialize для cppan::member, и в принципе должно работать.
Беда в том, что поддерживаются только те контейнеры которые есть во fusion-е. Хотя по идее можно было бы взять boost::enable_if<boost::fusion::is_sequence<T>>, и написать общий код для любых последовательностей. Кстати поддерживаются ли fusion view-хи, типа transform_view? Я плохо знаю тонкости сериализации, тут наверно есть какие-то свои подводные камни.
Здравствуйте, TarasKo, Вы писали:
TK>- Самый прямой путь это переопределить свободную функцию serialize, но тут как и с Boost.Serialization большая проблема. Мне нужно её переопределить для любого типа удовлетворяющего метафункции cppan::has_annotations. То есть что-то типа
TK>
TK>Но это не скомпилячится, потому что имеет такой же приоритет при разрешении перегрузки как и глобальный yas::serialize
эм.. если мной маразм еще не полностью завладел, то yas не использует глобальные функции serialize().
сериализаторы — это специализированные шаблоны serializer<T> с методом apply(Archive, T). типа:
так что, объясни как-то доходчивей, что ли...
кстати, yas доступен и на LWS: http://liveworkspace.org/code/3BOUco$0
TK>- Другой путь: поскольку cppan типы являются fusion последовательностями, то логично взять поддержку fusion-а которая есть в yas, определить serialize для cppan::member, и в принципе должно работать. TK>Беда в том, что поддерживаются только те контейнеры которые есть во fusion-е.
да, тут можно подумать над предоставлением сериализации не для fusion-конкретных типов, а для fusion-концептов...
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Да, я просто полез в примеры (те что в yas/example), а там нет нигде упоминания о struct serializer. Кроме примеров я больше никуда не смотрел.
Те не менее с этой структурой та же проблема, её не специализировать для множества сущностей, не хватает параметра typename Enable = void.
Здравствуйте, TarasKo, Вы писали:
TK>Да, я просто полез в примеры (те что в yas/example), а там нет нигде упоминания о struct serializer. Кроме примеров я больше никуда не смотрел.
там еще есть примеры, когда функция serialize() является мембером сериализуемого типа. (по аналогии с boost.serialization)
TK>Те не менее с этой структурой та же проблема, её не специализировать для множества сущностей, не хватает параметра typename Enable = void.
эта структура — приватная реализация. пользователю yas вообще не нужно о ней знать.
признаюсь, я все еще не понял сути проблемы %)
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Я не смог понять, как в yas неинтрузивно написать serialize не для одного класса, а сразу для всех классов, которые удовлетворяют некой метафункции.
Другими словами.
Хочу сериализовывать все свои классы. Есть библиотека yas, которая говорит, что, что бы сериализовать ваш класс, вам надо либо добавить в него метод serialize, либо написать свободную функцию serialize в своем namespace-e и через ADL она будет найдена и запользована yas-ом. Примеры первого и второго подхода лежат в yas/examples
У всех моих классов есть обобщённый механизм, позволяющий проитерироваться по всех их членам. Я не хочу в каждый свой класс добавлять функцию serialize. Я хочу написать одну свободную функцию serialize, которая бы использовалась для всех моих классов, через обобщённый механизм итерировалась по членам моих классов и передавала бы их в архив. При этом "Мой" или "не мой" класс, определяет метафункция cppan::has_annotations<T>.
Как это сделать?
Если бы это можно было сделать, это была бы мегафича, которой кстати нет в Boost.Serialize
Здравствуйте, TarasKo, Вы писали:
TK>Я не смог понять, как в yas неинтрузивно написать serialize не для одного класса, а сразу для всех классов, которые удовлетворяют некой метафункции.
сейчас нет такой возможности "искаропки".
с другой стороны, я думаю, если бы такая возможность и была, то это создало бы путаницу/неопределенность. нужно подумать...
TK>Как это сделать?
пока никак. подумаю над этим. возможно, увижу смысл рефакнуть проект...
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)