Алексей Чернигин — Магия метапрограммирования на...

66
1

description

Разрабатывая своё API, мы стараемся скрывать сторонние используемые библиотеки в реализации, чтобы не усложнять жизнь пользователям дополнительными зависимостями. Для этого приходится писать различные обёртки над библиотечными структурами данных и алгоритмами, делать внутри них конвертацию между форматами, согласовывать интерфейсы, создавать библиотечные объекты на основе наших собственных и выполнять другие избыточные действия. Благодаря своей шаблонной архитектуре библиотека Boost.Geometry способна работать с нашими структурами данных так же эффективно, как и со своими собственными, сводя накладные расходы на адаптацию к минимуму. Я расскажу о техниках современного метапрограммирования, которые используются в Boost.Geometry и делают возможной интеграцию алгоритмов этой библиотеки c нашими собственными структурами данных.

Transcript of Алексей Чернигин — Магия метапрограммирования на...

Page 1: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

1

Page 2: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

2

карты

Магия метапрограмирования. Boost.Geometry Алексей Чернигин

Page 3: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

3

Содержание

! Предыстория ! Почему именно generic

! Boost.Geometry – взгляд изнутри

Page 4: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

4

Предыстория

Page 5: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

5

Необходимые геометрические примитивы

Полигон

Ломаная Точка

Полигон с дырками Прямоугольник

Page 6: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

6

Необходимые геометрические алгоритмы

!  Пересечение двух геометрий !  Расстояние между двумя геометриями

!  Взаимное расположение двух геометрий !  Площадь

Page 7: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

7

Требования на библиотеку

При разработке мобильных приложений возникают следующие требования на используемые библиотеки и фреймворки:

!  Кроссплатформенность !  Свободная лицензия

!  Компактность !  Эффективность

Page 8: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

8

Существующие альтернативы

!  CGAL !  GEOS

! Boost.Geometry (GGL – Generic Geometry Library)

Page 9: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

9

А что, если мы разрабатываем не приложение, а библиотеку?

Page 10: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

10

Требования на библиотеку

Публичные заголовочные файлы не должны содержать типов из сторонних библиотек. В противном случае !  появляются дополнительные зависимости

!  возникает необходимость контролировать используемые версии библиотек

Page 11: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

11

Суровая правда жизни, или как выглядит типичная интеграция со сторонней библиотекой…

Page 12: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

12

Пример интеграции с GEOS

Конвертируем нашу точку Point в geos::Coordinate

struct GeoPoint { double latitude; double longitude; }; const Coordinate pointToGeosCoordinate(const GeoPoint& point) { return Coordinate(point.longitude, point.latitude); }

Page 13: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

13

Пример интеграции с GEOS

Конвертируем нашу полилинию Polyline в geos::LineString

struct Polyline { std::vector<GeoPoint> points; Polyline(); Polyline(const std::vector<GeoPoint>& points); }; LineString* polylineToGeosLineString(const Polyline& polyline) { CoordinateSequence coords; for (auto point : polyline.points) { coords.add(pointToGeosCoordinate(point)); } return geos::geom::GeometryFactory::getDefaultInstance()->createLineString(&coords); }

Page 14: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

14

Проблемы с производительностью

!  А если много полигонов? !  И каждый состоит из множества точек?

!  И они постоянно обновляются?

Page 15: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

15

А можно ли по-другому ?

Хотим писать так (псевдокод):

USE_MY_POINT_AS_YOUR_OWN(GeoPoint); USE_MY_LINESTRING_AS_YOUR_OWN(Polyline); int main() { GeoPoint point1(1.0, 1.0); GeoPoint point2(2.0, 2.0); Polyline polyline; polyline.points = { {-4, -3}, {5, -3}, {5, 3}, {-4, 3} }; double distance1 = 3rdparty::distance(point1, point2); double distance2 = 3rdparty::distance(point1, polyline); double distance3 = 3rdparty::distance(polyline, point1); if (distance2 != distance3) { std::cout << “Internal geometry library error” << std::endl; } return 0; }

Page 16: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

16

Нам нужна магия generic! Boost.Geometry?

Page 17: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

17

Расстояние между двумя точками

struct Point { double x; double y; }; namespace boost::geometry { double distance(const Point& a, const Point& b) { double dx = a.x - b.x; double dy = a.y - b.y; return sqrt(dx * dx + dy * dy); } };

Page 18: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

18

distance простая, но не generic

!  Работает только с конкретным типом Point !  Работает только в двухмерном пространстве

!  Работает только в декартовой системе координат !  Не работает с разными геометрическими примитивами

(например, точка и отрезок)

!  Точность всегда double

Page 19: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

19

Добавляем шаблоны

!  Можно использовать разные типы точек, но только те, где есть public поля x и y  !  Остальные проблемы на месте

template <typename P1, typename P2> inline double distance(const P1& a, const P2& b) { double dx = a.x - b.x; double dy = a.y - b.y; return sqrt(dx * dx + dy * dy); }

Page 20: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

20

Надо сделать generic access

Хотим, чтобы выглядело так:

namespace boost::geometry { template <typename P1, typename P2> inline double distance(const P1& a, const P2& b) { double dx = get<0>(a) - get<0>(b); double dy = get<1>(a) - get<1>(b); return sqrt(dx * dx + dy * dy); } }

Page 21: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

21

Generic access

// Добавляем access namespace boost::geometry::traits { template<typename P, int D> struct access {}; } namespace boost::geometry { template <int D, typename P> inline double get(const P& p) { return traits::access<P, D>::get(p); } }

Page 22: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

22

Применим к GeoPoint

namespace  boost::geometry::traits  {            template<>            struct  access<GeoPoint,  0>  {                  static  double  get(const  GeoPoint&  p)                    {                            return  p.longitude;                    }          };            template<>            struct  access<GeoPoint,  1>  {                  static  double  get(const  GeoPoint&  p)                    {                            return  p.latitude;                    }          };  }

Page 23: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

23

О метафункции

Дополнительно Boost накладывает ряд условий: !  Тип-член ::type является результатом вычисления метафункции metafunction<Arg1, Arg2>::type ~ function(Arg1,Arg2)

!  Тип-член ::value является результатом вычисления численной метафункции

Page 24: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

24

Обобщаем размерность // Метафункция в Boost.Geometry namespace boost::geometry { namespace traits { template<typename P> struct dimension {}; } template<typename P> struct dimension : traits::dimension<P> {}; } // Численная метафункция должна декларировать значение value (по определению) namespace boost::geometry::traits { template<> struct dimension<GeoPoint> : boost::mpl::int_<2>{};

Page 25: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

25

Эволюция функции distance namespace boost::geometry { // Было template <typename P1, typename P2> inline double distance(const P1& a, const P2& b) { double dx = a.x - b.x; double dy = a.y - b.y; return sqrt(dx * dx + dy * dy); } // Стало template <typename P1, typename P2> double distance(const P1& a, const P2& b) { BOOST_STATIC_ASSERT( dimension<P1>::value == dimension<P2>::value); return sqrt( pythagoras<P1, P2, dimension<P1>::value>::apply(a, b)); } }

Page 26: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

26

Рекурсивное инстанцирование template <typename P1, typename P2, int D> struct pythagoras { static double apply(const P1& a, const P2& b) { double d = get<D - 1>(a) - get<D - 1>(b); return d * d + pythagoras<P1, P2, D - 1>::apply(a, b); } }; template <typename P1, typename P2 > struct pythagoras<P1, P2, 0> { static double apply(const P1&, const P2&) { return 0; } };

Page 27: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

27

Промежуточные результаты

!  Можно использовать любой тип точки !  Независимо от размерности пространства

!  Пока все еще нет независимости от системы координат – добавим соответствующие traits классы

Проявляется концепция точки – таким же образом можно описывать концепции других примитивов

Page 28: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

28

Концепция Point (не финальная)

! Класс traits::access, содержащий функцию get!! Специализация для класса traits::dimension

! Специализация для класса traits::coordinate_system ! Специализация для класса traits::coordinate_type

Page 29: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

29

Адаптация GeoPoint для концепции точки

namespace boost::geometry::traits { template<> struct dimension<GeoPoint> : boost::mpl::int_<2> {}; template<> struct coordinate_type<GeoPoint> { typedef double type; }; template<> struct coordinate_system<GeoPoint> { typedef cs::cartesian type; }; template<> struct access<GeoPoint, 0> { static inline double get(const GeoPoint& p) { return p.longitude; } static inline void set(GeoPoint& p, const double& value) { p.longitude = value; } }; template<> struct access<GeoPoint, 1> { static inline double get(const GeoPoint& p) { return p.latitude; } static inline void set(GeoPoint& p, const double& value) { p.latitude = value; } }; }

Page 30: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

30

Адаптация GeoPoint для концепции точки

namespace boost::geometry::traits { template<> struct dimension<GeoPoint> : boost::mpl::int_<2> {}; template<> struct coordinate_type<GeoPoint> { typedef double type; }; template<> struct coordinate_system<GeoPoint> { typedef cs::cartesian type; }; template<> struct access<GeoPoint, 0> { static inline double get(const GeoPoint& p) { return p.longitude; } static inline void set(GeoPoint& p, const double& value) { p.longitude = value; } }; template<> struct access<GeoPoint, 1> { static inline double get(const GeoPoint& p) { return p.latitude; } static inline void set(GeoPoint& p, const double& value) { p.latitude = value; } }; } // Или даже проще (заменив все на соответствующий макрос) BOOST_GEOMETRY_REGISTER_POINT_2D(GeoPoint, double, cs::cartesian, longitude, latitude)

Page 31: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

31

Концепция LineString (не финальная)

По аналогии с точкой определим концепцию LineString !  Совместимо с Boost.Range с произвольным доступом

!  Специализация итераторов range_iterator и range_const_iterator!

!  Переопределение функций range_begin и range_end!!  Тип, определяемый метафункцией range_value<…>::type, должен удовлетворять концепции Point

Page 32: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

32

Адаптация Polyline namespace boost { template<> struct range_iterator<Polyline> { typedef std::vector<GeoPoint>::iterator type; }; template<> struct range_const_iterator<Polyline> { typedef std::vector<GeoPoint>::const_iterator type; }; } inline std::vector<GeoPoint>::iterator range_begin(Polyline& polyline) { return polyline.points.begin(); } inline std::vector<GeoPoint>::iterator range_end(Polyline& polyline) { return polyline.points.end(); } inline std::vector<GeoPoint>::const_iterator range_begin( const Polyline& polyline) { return polyline.points.begin(); } inline std::vector<GeoPoint>::const_iterator range_end( const Polyline& polyline) { return polyline.points.end(); }

Page 33: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

33

Что в итоге ?

Для двух точек distance работает

BOOST_GEOMETRY_REGISTER_POINT_2D(GeoPoint, double, cs::cartesian, longitude, latitude) // на этом месте находится адаптация Polyline … int main () { GeoPoint point1(1.0, 1.0); GeoPoint point2(2.0, 2.0); Polyline polyline; polyline.points = { {-4, -3}, {5, -3}, {5, 3}, {-4, 3} }; double distance1 = boost::geometry::distance(point1, point2); }

Page 34: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

34

Что в итоге ?

Для двух точек distance работает, а для двух геометрий разных типов — нет

BOOST_GEOMETRY_REGISTER_POINT_2D(GeoPoint, double, cs::cartesian, longitude, latitude) // на этом месте находится адаптация Polyline … int main () { GeoPoint point1(1.0, 1.0); GeoPoint point2(2.0, 2.0); Polyline polyline; polyline.points = { {-4, -3}, {5, -3}, {5, 3}, {-4, 3} }; double distance1 = boost::geometry::distance(point1, point2); // не cкомпилируется – не определены соответствующие специализации double distance2 = boost::geometry::distance(point1, polyline); // не скомпилируется – обратный порядок аргументов не поддерживается double distance3 = boost::geometry::distance(polyline, point1); }

Page 35: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

35

Добавляем геометрические примитивы Можно определить разные функции с именами, соответствующими передаваемым геометрическим примитивам template <typename P1, typename P2> double distance_point_point(const P1& point1, const P2& point2) {…} template <typename P, typename L> double distance_point_linestring(const P& point, const L& linestring) {…} template <typename L, typename P> double distance_linestring_point(const L& linestring, const P& point) {…}

Page 36: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

36

Добавляем геометрические примитивы Можно определить разные функции с именами, соответствующими передаваемым геометрическим примитивам template <typename P1, typename P2> double distance_point_point(const P1& point1, const P2& point2) {…} template <typename P, typename L> double distance_point_linestring(const P& point, const L& linestring) {…} template <typename L, typename P> double distance_linestring_point(const L& linestring, const P& point) {…}

GeoPoint point1(1.0, 1.0); GeoPoint point2(2.0, 2.0) double distance1 = distance(point1, point2); Polyline polyline; polyline.points = { {-4, -3}, {5, -3}, {5, 3}, {-4, 3} }; double distance2 = distance(point1, polyline);

 

Но мы хотим писать generic код

Page 37: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

37

Добавляем геометрические примитивы

Можно было бы определить разные перегрузки функции для разных пар геометрических примитивов

template <typename Point1, typename Point2> double distance(const Point1& point1, const Point2& point2); template <typename Point, typename Linestring> double distance(const Point& point, const Linestring& linestring);

Page 38: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

38

Добавляем геометрические примитивы

Можно было бы определить разные перегрузки функции для разных пар геометрических примитивов

template <typename Point1, typename Point2> double distance(const Point1& point1, const Point2& point2); template <typename Point, typename Linestring> double distance(const Point& point, const Linestring& linestring);

Но при инстанцировании возникнет неоднозначность выбора

int main() { GeoPoint point(1.0, 1.0); Polyline polyline; polyline.points = { {-4, -3}, {5, -3}, {5, 3}, {-4, 3} }; double distance = distance(point1, polyline); // ошибка - неоднозначность }

Page 39: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

39

Есть 2 решения

!  SFINAE (Substitution Failure Is Not An Error) !  Tag dispatching

Page 40: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

40

SFINAE Невозможность подстановки не является ошибкой

template<typename T> typename T::type foo(T t) { return T::type(); } template<typename T> T foo(T t) { return t; } int main() { foo(3); //T выводится как int. В результате вызовется вторая перегрузка return 0; }

Page 41: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

41

SFINAE !  Использовалось в первой версии библиотеки !  Имеет ряд ограничений template<typename T> struct bar { typedef typename T::type type; }; template<typename T> typename bar<T>::type foo2(T t) { return bar<T>::type(); } template<typename T> T foo2(T t) { return T(); }  int main() { foo2(3); return 0; } Error:  type  ‘int’  cannot  be  used  prior  to  ‘::’  because  it  has  no  members  …  note:  in  instantion  of  template  class  ‘bar<int>’  requested  here  …  note:  while  substituting  deduced  template  arguments  into  function  template  ‘foo2’…  

Page 42: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

42

Tag dispatching !  Tag dispatching – способ использования перегрузки функций для реализации концепций, при этом тэгом называется пустая структура или класс.

namespace boost::geometry { struct point_tag {};

struct linestring_tag {};

namespace traits { template<typename G> struct tag {}; template<> struct tag<GeoPoint> { typedef point_tag type; }; template<> struct tag<Polyline> { typedef linestring_tag type; }; } }

Page 43: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

43

Концепция Point (финальная)

5 классов свойств сформировали концепцию «Точка»: !  4 метафункции

–  Специализация для класса traits::tag –  Специализация для класса traits::coordinate_system –  Специализация для класса traits::coordinate_type –  Специализация для класса traits::dimension !  Класс traits::access, содержащий функцию get

Page 44: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

44

Адаптация концепции точки (revisited)

namespace boost::geometry::traits { template<> struct tag<GeoPoint> { typedef point_tag type; }; template<> struct dimension<GeoPoint> : boost::mpl::int_<2> {}; template<> struct coordinate_type<GeoPoint> { typedef double type; }; template<> struct coordinate_system<GeoPoint> { typedef cs::cartesian type; }; template<> struct access<GeoPoint, 0> { static inline double get(const GeoPoint& p) { return p.longitude; } static inline void set(GeoPoint& p, const double& value) { p.longitude = value; } }; template<> struct access<GeoPoint, 0> { static inline double get(const GeoPoint& p) { return p.longitude; } static inline void set(GeoPoint& p, const double& value) { p.longitude = value; } }; } // Или даже проще (заменив все на соответствующий макрос) BOOST_GEOMETRY_REGISTER_POINT_2D(GeoPoint, double, cs::cartesian, longitude, latitude)

Page 45: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

45

Tag dispatching by instance namespace boost::geometry { namespace dispatch { template<typename G1, typename G2> double distance(const G1& g1, const G2& g2, point_tag, point_tag) { //… } template<typename G1, typename G2> double distance(const G1& g1, const G2& g2, point_tag, linestring_tag) { //… } } template<typename G1, typename G2> double distance(const G1& g1, const G2& g2) { typename tag<G1>::type tag1; typename tag<G2>::type tag2; return dispatch::distance(g1, g2, tag1, tag2); } }

Page 46: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

46

Хорошее решение. А можно ли еще лучше?

Page 47: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

47

Tag dispatching by type

Заменим функции на структуры и соответствующие статические функции-члены

namespace boost::geometry::dispatch { template<typename Tag1, typename Tag2, typename G1, typename G2> struct distance {}; template<typename P1, typename P2> struct distance<point_tag, point_tag, P1, P2> { static double apply(const P1& a, const P2& b) { … } }; template<typename P1, typename P2> struct distance<point_tag, linestring_tag, P1, P2> { static double apply(const P1& a, const P2& b) { … } }; }

Page 48: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

48

Tag dispatching by type

Нет необходимости создавать экземпляры тэгов

namespace boost::geometry { template <typename G1, typename G2> double distance(const G1& g1, const G2& g2) { return dispatch::distance < typename tag<G1>::type, typename tag<G2>::type, G1, G2 >::apply(g1, g2); } }

Page 49: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

49

Возможности tag dispatching by type

!  Можно переопределять типы (coordinate_type и coordinate_system)

!  Можно переопределять константы (dimension)

!  Легко реализовать реверсивный порядок аргументов

Page 50: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

50

Добавляем геометрические примитивы

Все геометрические примитивы состоят из точек, и, как следствие, наследуют ряд их свойств: !  размерность пространства;

!  систему координат; !  тип координат (int, double и т.д.)

Поэтому не хотим писать такие же классы свойств, как и для точки, для всех геометрических примитивов. Хотим использовать уже написанные для точки.

Page 51: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

51

Определим тип точки для геометрий namespace boost::geometry { namespace traits { template<typename Geometry> struct point_type {}; } namespace dispatch { template<typename Tag, typename Geometry> struct point_type { typedef typename traits::point_type<Geometry>::type type; }; template<typename Point> struct point_type<point_tag, Point> { typedef Point type; }; template<typename Linestring> struct point_type<linestring_tag, Linestring> { typedef typename boost::range_value<Linestring>::type type; }; } template<typename Geometry> struct point_type { typedef typename dispatch::point_type <typename tag<Geometry>::type, Geometry>::type type; }; }

Page 52: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

52

Переопределим dimension

Для точки оставляем так, как было сделано ранее

// Метафункция в Boost.Geometry namespace boost::geometry { namespace traits { template<typename P> struct dimension {}; } template<typename P> struct dimension : traits::dimension<P> {}; } namespace boost::geometry::traits { template<> struct dimension<GeoPoint> : boost::mpl::int_<2>{};

Page 53: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

53

Переопределим dimension

Переопределим dimension для других геометрических примитивов, используя dimension для точки

namespace boost::geometry { // диспетчеризующая метафункция зависит от типа геометрии и от тэга namespace dispatch { template <typename Tag, typename G> struct dimension : dimension<point_tag, typename point_type<Tag, G>::type> {}; template <typename P> struct dimension<point_tag, P> : traits::dimension<P> {}; } // внешняя метафункция зависит только от типа геометрии template <typename G> struct dimension : dispatch::dimension<typename tag<G>::type, G> {};

Page 54: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

54

Переопределим систему координат namespace boost::geometry { namespace traits { template<typename Point> struct coordinate_system {}; template<> struct coordinate_system<GeoPoint> { typedef cs::cartesian type; }; } namespace dispatch { template<typename Tag, typename Geometry> struct coordinate_type { typedef typename point_type<Tag, Geometry>::type point_type; // вызов собственной специализации для point_tag typedef typename coordinate_system<point_tag, point_type>::type type; }; template<typename Point> struct coordinate_system<point_tag, Point> { typedef typename traits::coordinate_system<Point>::type type; }; } template <typename Geometry> struct coordinate_system { typedef typename dispatch::coordinate_system <typename tag<Geometry>::type, Geometry>::type type; }; }

Page 55: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

55

Переопределим тип координат namespace boost::geometry { namespace traits { template<typename Point> struct coordinate_type {}; template<> struct coordinate_type<GeoPoint> { typedef double type; }; } namespace dispatch { template<typename Tag, typename Geometry> struct coordinate_type { typedef typename point_type<Tag, Geometry>::type point_type; // вызов собственной специализации для point_tag typedef typename coordinate_type<point_tag, point_type>::type type; }; template<typename Point> struct coordinate_type<point_tag, Point> { typedef typename traits::coordinate_type<Point>::type type; }; } template <typename Geometry> struct coordinate_type { typedef typename dispatch::coordinate_type <typename tag<Geometry>::type, Geometry>::type type; }; }

Page 56: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

56

Обобщаем тип координат

template <typename P1, typename P2, int D> struct pythagoras { typedef typename select_most_precise < typename coordinate_type<P1>::type, typename coordinate_type<P2>::type >::type selected_type; static selected_type apply(const P1& a, const P2& b) { selected_type d = get<D-1>(a) - get<D-1>(b); return d * d + pythagoras<P1, P2, D-1>::apply(a, b); } };

Page 57: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

57

Реверсивный порядок аргументов

!  Если 𝑖𝑑G2>𝑖𝑑𝐺1, то надо обратить порядок аргументов !  Явно указываем, что если 𝑖𝑑G2=𝑖𝑑𝐺1, то не нужно обращать порядок аргументов.

namespace boost::geometry { namespace dispatch { template<typename Tag> struct geometry_id {}; template<> struct geometry_id<point_tag> : boost::mpl::int_<1> {}; template<> struct geometry_id<linestring_tag> : boost::mpl::int_<2> {}; } template<typename Geometry> struct geometry_id : dispatch::geometry_id<typename tag<Geometry>::type> {}; }

Page 58: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

58

Реверсивный порядок аргументов

!  Если G2, то надо обратить порядок аргументов !  Явно указываем, что если G2, то не нужно обращать порядок аргументов

namespace boost::geometry { template <typename G1, typename G2> struct reverse_dispatch : boost::mpl::if_c < geometry_id<G1>::value > geometry_id<G2>::value true_type, false_type > {}; template <typename Geometry> struct reverse_dispatch<Geometry, Geometry> : boost::false_type {}; }

Page 59: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

59

Изменяем distance namespace boost::geometry::dispatch { template<typename Geometry1, typename Geometry2, typename Tag1, typename Tag2, bool Reverse = reverse_dispatch<Geometry1, Geometry2>::type::value> struct distance {}; template<typename Geometry1, typename Geometry2, typename Tag1, typename Tag2> struct distance<Geometry1, Geometry2, Tag1, Tag, true> : distance<Geometry2, Geometry1, Tag2, Tag1, false> { static inline return_type apply( const Geometry1& g1, const Geometry2& g2) { // шаблон функции имеет частичную специализацию для каждой прямой /// пары тэгов return distance < Geometry2, Geometry1, Tag2, Tag1, false >::apply(g2, g1, strategy); } };

Page 60: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

60

Изменяем distance

Специализируем distance для прямого порядка аргументов

namespace boost::geometry::dispatch { // Point-Point template <typename Point1, typename Point2> struct distance<Point1, Point2, point_tag, point_tag, false> static inline return_type apply(const Point1& point1, const Point2& point2) { … } }; // Point-Linestring template <typename Point, typename Linestring> struct distance<Point, Linestring, point_tag, linestring_tag, false> { static inline return_type apply(const Point& point, const Linestring& linestring) { … } }; }

Page 61: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

61

Результаты

Page 62: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

62

Результаты

Шаблонная магия сделала возможным использование своих структур данных с алгоритмами Boost.Geometry

BOOST_GEOMETRY_REGISTER_POINT_2D( GeoPoint, double, cs::cartesian, longitude, latitude) BOOST_GEOMETRY_REGISTER_LINESTRING(Polyline) // int main() { GeoPoint point1(1.0, 1.0); GeoPoint point2(2.0, 2.0); Polyline polyline; polyline.points = { {-4, -3}, {5, -3}, {5, 3}, {-4, 3} }; double distance1 = 3rdparty::distance(point1, point2); double distance2 = 3rdparty::distance(point1, polyline); double distance3 = 3rdparty::distance(polyline, point1); }

Page 63: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

63

Результаты

!  Малый объем дополнительного кода (несколько классов traits)

!  Практически нулевой overhead на производительность

!  Абсолютно generic код, включая возможность обратного порядка аргументов

Page 64: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

64

Вопросы?

Page 65: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

65

Спасибо за внимание!

Page 66: Алексей Чернигин — Магия метапрограммирования на примере Boost.Geometry

66

Алексей Чернигин

Яндекс.Карты

[email protected]

Разработчик