Reflection в С++
Александр Фокин
Логотип партнераЛоготип Яндекса (Сервиса)
3
План
1. Что такое Reflection?
2. Reflection наших дней.
3. Единый Reflection API дней грядущих.
4. Рефлексируем над услышанным.
Что такое Reflection?
4
5
Reflection в C++
• Получение свойств типов. Перечисление членов классов и их свойств. НЕ <type_traits>, НЕ has_xxx тесты через SFINAE.
Хранение и обработка свойств типов стандартными средствами языка.• Получение reflection данных в runtime в виде
классов.• Получение reflection данных в виде compile-time
структур.
6
Reflection в C++
• Получение свойств типов. Перечисление членов классов и их свойств. НЕ <type_traits>, НЕ has_xxx тесты через SFINAE.
• Хранение и обработка свойств типов стандартными средствами языка. Получение reflection данных в runtime в виде
классов. Получение reflection данных в виде compile-time
структур.
7
Runtime Reflection
void dump(QObject *o, QTextStream& s) { QMetaObject *m = o->metaObject();
for(int i = 0; i < m->propertyCount(); i++) { QMetaProperty p = m->property(i);
s << p.name() << “=” << p.read(o).toString() << endl; }}
8
Compile-time Reflectiontemplate<int N>using int_ = std::integral_constant<int, N>;
template<class T>void dump(const T& o, std::ostream& s) { using fields = fieldsof(T);
do_dump(o, s, int_<fields::size - 1>());}
template<class T, int N>void do_dump(const T& o, std::ostream& s, int_<N>) { using field = typename fieldsof(T)::template at<N>;
s << field::name() << “=” << field::getter()(o) << std::endl; do_dump(o, s, int_<N - 1>());}
template<class T>void do_dump(const T&, std::ostream&, int_<-1>) {}
9
Зачем программисту Reflection?
• Сериализация в XML / JSON / BSON / UBJSON / CSV / все что душе угодно.
• ORM для вашей любимой БД.
• Автоматическая генерация кода (operator==, и т.п.).
• Доступ к данным из скриптов / конфигов.
• «Data-driven programming» в широком смысле.
10
Зачем программисту Reflection?
• Сериализация в XML / JSON / BSON / UBJSON / CSV / все что душе угодно.
• ORM для вашей любимой БД.
• Автоматическая генерация кода (operator==, и т.п.).
• Доступ к данным из скриптов / конфигов.
• «Data-driven programming» в широком смысле.
11
Зачем программисту Reflection?
• Сериализация в XML / JSON / BSON / UBJSON / CSV / все что душе угодно.
• ORM для вашей любимой БД.
• Автоматическая генерация кода (operator== и т.п.).
• Доступ к данным из скриптов / конфигов.
• «Data-driven programming» в широком смысле.
12
Зачем программисту Reflection?
• Сериализация в XML / JSON / BSON / UBJSON / CSV / все что душе угодно.
• ORM для вашей любимой БД.
• Автоматическая генерация кода (operator== и т.п.).
• Доступ к данным из скриптов / конфигов.
• .
13
Зачем программисту Reflection?• Сериализация в XML / JSON / BSON / UBJSON / CSV / все
что душе угодно.
• ORM для вашей любимой БД.
• Автоматическая генерация кода (operator== и т.п.).
• Доступ к данным из скриптов / конфигов.
• Преобразования типов (вектор структур в структуру из векторов и т.п.).
• …
Reflection наших дней
14
15
Подходы
• Куча runtime велосипедов библиотек. CPGF, XCppRefl, Ponder…
• Workaround’ы на уровне фреймворков. Qt Moc.
• Куча domain-specific библиотек. Cereal, ODB, QxOrm, …
• Кирпичики для велосипедостроения Boost.Fusion / Boost.Hana / Boost.Reflect / Mirror.
• Полный DIY, или «сделай сам». Наиболее часто встречающийся в жизни подход.
16
Подходы
• Куча runtime велосипедов библиотек. CPGF, XCppRefl, Ponder…
• Workaround’ы на уровне фреймворков. Qt Moc.
• Куча domain-specific библиотек. Cereal, ODB, QxOrm, …
• Кирпичики для велосипедостроения Boost.Fusion / Boost.Hana / Boost.Reflect / Mirror.
• Полный DIY, или «сделай сам». Наиболее часто встречающийся в жизни подход.
17
Подходы
• Куча runtime велосипедов библиотек. CPGF, XCppRefl, Ponder…
• Workaround’ы на уровне фреймворков. Qt Moc.
• Куча domain-specific библиотек. Cereal, ODB, QxOrm, …
• Кирпичики для велосипедостроения Boost.Fusion / Boost.Hana / Boost.Reflect / Mirror.
• Полный DIY, или «сделай сам». Наиболее часто встречающийся в жизни подход.
18
Подходы
• Куча runtime велосипедов библиотек. CPGF, XCppRefl, Ponder…
• Workaround’ы на уровне фреймворков. Qt Moc.
• Куча domain-specific библиотек. Cereal, ODB, QxOrm, …
• Кирпичики для велосипедостроения Boost.Fusion / Boost.Hana / Boost.Reflect / Mirror.
• Полный DIY, или «сделай сам». Наиболее часто встречающийся в жизни подход.
19
Подходы
• Куча runtime велосипедов библиотек. CPGF, XCppRefl, Ponder…
• Workaround’ы на уровне фреймворков. Qt Moc.
• Куча domain-specific библиотек. Cereal, ODB, QxOrm, …
• Кирпичики для велосипедостроения Boost.Fusion / Boost.Hana / Boost.Reflect / Mirror.
• Полный DIY, или «сделай сам». Наиболее часто встречающийся в жизни подход.
20
Мотивирующий пример• Один макрос генерирует 14 функций:
функции (де)сериализации в xml, json, ubjson, cvs; ORM-обвязки для sqlite; функции сравнения и вычисления хеша; функцию отладочной печати.
• Минус 100+ строк копипасты и отсутствие боли при необходимости что-то изменить в классе.
struct ApiLicenseData { Latin1Array key; Latin1Array licenseBlock;};#define ApiLicenseData_Fields (key)(licenseBlock)
ADAPT_STRUCT_FUNCTIONS( ApiLicenseData, (xml)(json)(ubjson)(csv_record)(sql_record)(eq)(less)(hash)(debug));
21
Реализация на Boost.Fusion
struct ApiLicenseData { Latin1Array key; Latin1Array licenseBlock;};
BOOST_FUSION_ADAPT_STRUCT( ApiLicenseData, (Latin1Array, key) (Latin1Array, licenseBlock));
22
Реализация на Boost.Fusion
template<class T>void serialize(const T &value, std::ostream &s) { using range = boost::mpl::range_c< size_t, 0, boost::fusion::result_of::size<T>::value>;
boost::fusion::for_each(range(), [&](auto arg) { constexpr size_t index = decltype(arg)::value;
auto name = boost::fusion::extension:: struct_member_name<T, index>::call();
s << "<" << name << ">"; serialize(boost::fusion::at_c<index>(value), s); s << "</" << name << ">"; });}
23
Реализация на Boost.Fusion
using range = boost::mpl::range_c< size_t, 0, boost::fusion::result_of::size<T>::value>;
Эквивалентно:
using range = boost::mpl::vector< std::integral_constant<size_t, 0>, std::integral_constant<size_t, 1>, /* ... */>;
24
Реализация на Boost.Fusion
boost::fusion::for_each(range(), lambda);
Эквивалентно:
lambda(std::integral_constant<size_t, 0>());lambda(std::integral_constant<size_t, 1>());/* ... */
25
Реализация на Boost.Fusion
template<class T>void serialize(const T &value, std::ostream &s) { using range = boost::mpl::range_c< size_t, 0, boost::fusion::result_of::size<T>::value>;
boost::fusion::for_each(range(), [&](auto arg) { constexpr size_t index = decltype(arg)::value;
auto name = boost::fusion::extension:: struct_member_name<T, index>::call();
s << "<" << name << ">"; serialize(boost::fusion::at_c<index>(value), s); s << "</" << name << ">"; });}
26
Что-то посложнее на Boost.Fusion
class QPoint {public: int x() const; int y() const; void setX(int x); void setY(int y); /* ... */};
BOOST_FUSION_ADAPT_ADT(QPoint, (int, int, obj.x(), obj.setX(val)) (int, int, obj.y(), obj.setY(val)))
int main() { QPoint point(0, 1); serialize(point, std::cout);}
27
Что-то посложнее на Boost.Fusion
class QPoint {public: int x() const; int y() const; void setX(int x); void setY(int y); /* ... */};
BOOST_FUSION_ADAPT_ADT(QPoint, (int, int, obj.x(), obj.setX(val)) (int, int, obj.y(), obj.setY(val)))
int main() { QPoint point(0, 1); serialize(point, std::cout);}
error: use of undefined type'boost::fusion::extension::struct_member_name<T,0>'
28
Что-то посложнее на Boost.Fusion
class QPoint {public: int x() const; int y() const; void setX(int x); void setY(int y); /* ... */};
BOOST_FUSION_ADAPT_ADT(QPoint, (int, int, obj.x(), obj.setX(val)) (int, int, obj.y(), obj.setY(val)))
int main() { QPoint point(0, 1); serialize(point, std::cout);}
CHALLENGE ACCEPTED
error: use of undefined type'boost::fusion::extension::struct_member_name<T,0>'
29
Что-то посложнее на Boost.Fusion
class QPoint {public: int x() const; int y() const; void setX(int x); void setY(int y); /* ... */};
MY_ADAPT_ADT(QPoint, ((x, int, int, obj.x(), obj.setX(val))) ((y, int, int, obj.y(), obj.setY(val))))
int main() { QPoint point(0, 1); serialize(point, std::cout);}
30
Что-то посложнее на Boost.Fusion
#define MY_ADAPT_ADT(TYPE, PARAMS) \ BOOST_FUSION_ADAPT_ADT(TYPE, MY_TO_FUSION_PARAMS(PARAMS)) \ MY_ADAPT_ADT_I(TYPE, PARAMS)
/** * ((a, b, c, d, e))((a, b, c, d, e)) -> (b, c, d, e)(b, c, d, e) */#define MY_TO_FUSION_PARAMS(PARAMS) \ BOOST_PP_SEQ_FOR_EACH(MY_TO_FUSION_PARAMS_I, ~, PARAMS)
/** * (a, b, c, d, e) -> (b, c, d, e) */#define MY_TO_FUSION_PARAMS_I(R, D, PARAM) \ BOOST_PP_TUPLE_POP_FRONT(PARAM)
31
Что-то посложнее на Boost.Fusion
#define MY_ADAPT_ADT(TYPE, PARAMS) \ BOOST_FUSION_ADAPT_ADT(TYPE, MY_TO_FUSION_PARAMS(PARAMS)) \ MY_ADAPT_ADT_I(TYPE, PARAMS)
#define MY_ADAPT_ADT_I(TYPE, PARAMS) \ BOOST_PP_SEQ_FOR_EACH_I(MY_ADAPT_ADT_II, TYPE, PARAMS)
#define MY_ADAPT_ADT_II(R, TYPE, INDEX, PARAM) \namespace boost { namespace fusion { namespace extension { \ template<> \ struct struct_member_name<TYPE, INDEX> { \ typedef char const *type; \ static type call() { \ return BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(0, PARAM));\ } \ }; \}}}
32
Что-то посложнее на Boost.Fusion
class QPoint {public: int x() const; int y() const; void setX(int x); void setY(int y); /* ... */};
MY_ADAPT_ADT(QPoint, ((x, int, int, obj.x(), obj.setX(val))) ((y, int, int, obj.y(), obj.setY(val))))
int main() { QPoint point(0, 1); serialize(point, std::cout);}
error: 'size': is not a member of 'boost::fusion::extension::adt_attribute_proxy<QPoint,0,true>'
33
Что-то посложнее на Boost.Fusion
template <typename T, int N, bool Const>void serialize( const boost::fusion::extension:: adt_attribute_proxy<T, N, Const>& value, std::ostream &s) { s << value.get();}
34
Что-то посложнее на Boost.Fusion
template <typename T, int N, bool Const>void serialize( const boost::fusion::extension:: adt_attribute_proxy<T, N, Const>& value, std::ostream &s) { s << value.get();}
int main() { QPoint point(0, 1); serialize(point, std::cout);}
<x>0</x><y>1</y>
35
Phew!
Image © Energy Live News
PHEW!
36
Взгляд со стороны“This is what you get when C++ meets Reflection.
Whatever you choose, it'll probably have horrible macros, hard to debug code or weird build steps.”
–Skizz @ stackoverflow
Image © Wikimedia commons
37
Проблемы
• Нет единого Reflection API.
• Приходится использовать макросы и нарушать DRY.
• Нет ни одного готового решения / фреймворка, в котором все работало бы «из коробки».
38
Проблемы
• Нет единого Reflection API.
• Приходится использовать макросы и нарушать DRY.
• Нет ни одного готового решения / фреймворка, в котором все работало бы «из коробки».
39
Проблемы
• Нет единого Reflection API.
• Приходится использовать макросы и нарушать DRY.
• Нет ни одного готового решения / фреймворка, в котором все работало бы «из коробки»*.
40
Как должно быть
Стандартный механизм аннотированияReflection API
Фреймворки для (де)сериализации
RPC и все, что душе угодно
Стан
дарт
ные
сред
ства
С+
+
Библ
иоте
киПо
льзо
вате
льс
кий
код Аннотации для
составных пользовательских
типов
Реализация стандартных функций
для элементарных пользовательских
типов
Единый Reflection API дней грядущих
41
42
Дизайн
Насколько глубокий Reflection действительно нужен?
• Нужен ли Reflection для шаблонов?
• А для переменных?
• А для шаблонных переменных?
• А для namespace’ов?
• А для private полей?
43
Дизайн
Насколько глубокий Reflection действительно нужен?
• Нужен ли Reflection для шаблонов?
• А для переменных?
• А для шаблонных переменных?
• А для namespace’ов?
• А для private полей?
44
Дизайн
Насколько глубокий Reflection действительно нужен?
• Нужен ли Reflection для шаблонов?
• А для переменных?
• А для шаблонных переменных?
• А для namespace’ов?
• А для private полей?
Image © Wikifur wiki
45
Дизайн
Насколько глубокий Reflection действительно нужен?
• Нужен ли Reflection для шаблонов?
• А для переменных?
• А для шаблонных переменных?
• А для namespace’ов?
• А для private полей?
46
Дизайн
Насколько глубокий Reflection действительно нужен?
• Нужен ли Reflection для шаблонов?
• А для переменных?
• А для шаблонных переменных?
• А для namespace’ов?
• А для private полей?
47
Предложения в стандарт• N4428, Type Property Queries (Andrew Tomazos &
Christian Kaeser). Легковесный Reflection. Простой API а-ля <type_traits>.
• N4447, From a type T, gather members name and type information, via variadic template expansion (Cleiton Santoia Silva & Daniel Auresco). Легковесный Reflection. Простой API с синтаксисом typeid<T>...,
обсуждение атрибутов.• N4451, Static Reflection (Matus Chochlik).
Глубокий Reflection. API на операторе reflected и возвращаемых им
типах.
48
Предложения в стандарт• N4428, Type Property Queries (Andrew Tomazos &
Christian Kaeser). Легковесный Reflection. Простой API а-ля <type_traits>.
• N4447, From a type T, gather members name and type information, via variadic template expansion (Cleiton Santoia Silva & Daniel Auresco). Легковесный Reflection. Простой API с синтаксисом typeid<T>...,
обсуждение атрибутов.• N4451, Static Reflection (Matus Chochlik).
Глубокий Reflection. API на операторе reflected и возвращаемых им
типах.
49
Предложения в стандарт• N4428, Type Property Queries (Andrew Tomazos &
Christian Kaeser). Легковесный Reflection. Простой API а-ля <type_traits>.
• N4447, From a type T, gather members name and type information, via variadic template expansion (Cleiton Santoia Silva & Daniel Auresco). Легковесный Reflection. Простой API с синтаксисом typeid<T>...,
обсуждение атрибутов.• N4451, Static Reflection (Matus Chochlik).
Глубокий Reflection. API на операторе reflected и возвращаемых им
типах.
50
Type Property Queries
• Добавляет два новых трейта: std::class_traits – для доступа к информации о
классах. std::enum_traits – для доступа к информации о
перечислениях.• Существует реализация для clang.
• Есть план дальнейшего развития с поддержкой namespace’ов и шаблонов.
51
Reflection для классов
template<class C>struct class_traits { struct base_classes { static constexpr size_t size;
template<size_t I> struct get { typedef /* ... */ type; static constexpr bool is_virtual; }; };
/* ... */};
52
Reflection для классов
template<class C>struct class_traits { /* ... */
struct class_members { static constexpr size_t size;
template<size_t I> struct get { static constexpr string_literal name; static constexpr /* ... */ pointer; }; };
/* ... */};
53
Reflection для классов
template<class T>void serialize(const T &value, std::ostream &s) { using members = typename std::class_traits<T>::class_members; using range = boost::mpl::range_c<size_t, 0, members::size>;
boost::fusion::for_each(range(), [&](auto arg) { constexpr size_t index = decltype(arg)::value;
auto name = members::template get<index>::name; auto pointer = members::template get<index>::pointer;
s << "<" << name << ">"; serialize(value.*pointer, s); s << "</" << name << ">"; });}
54
Будущее
1. Стандартизации в C++17 ждать не стоит. Но получить Reflection в одном из TS после C++17 шансы вполне есть.
2. После стандартизации появятся библиотеки, использующие стандартный API.
3. …
4. PROFIT!
Вопросы?
55
Контакты
+7 915 3705385 retgone
retgone
Александр Фокин
Руководитель службы разработки поисковых компонент
Спасибо!
57
Addendum
60
Reflection для enum’ов
template<class E>struct enum_traits { struct enumerators { static constexpr size_t size;
template<size_t I> struct get { static constexpr string_literal identifier; static constexpr E value; }; };};