1
2 
карты 
Магия 
метапрограмирования. 
Boost.Geometry 
Алексей Чернигин
3 
Содержание 
! Предыстория 
! Почему именно generic 
! Boost.Geometry – взгляд изнутри
4 
Предыстория
5 
Необходимые геометрические 
примитивы 
Точка Ломаная 
Прямоугольник Полигон 
Полигон с дырками
6 
Необходимые геометрические 
алгоритмы 
! Пересечение двух геометрий 
! Расстояние между двумя геометриями 
! Взаимное р...
7 
Требования на библиотеку 
При разработке мобильных приложений возникают 
следующие требования на используемые библиотек...
8 
Существующие альтернативы 
! CGAL 
! GEOS 
! Boost.Geometry (GGL – Generic Geometry Library)
9 
А что, если мы разрабатываем 
не приложение, а библиотеку?
10 
Требования на библиотеку 
Публичные заголовочные файлы не должны содержать типов 
из сторонних библиотек. В противном ...
11 
Суровая правда жизни, или как 
выглядит типичная интеграция 
со сторонней библиотекой…
12 
Пример интеграции с GEOS 
Конвертируем нашу точку Point в geos::Coordinate 
struct GeoPoint { 
double latitude; 
doubl...
13 
Пример интеграции с GEOS 
Конвертируем нашу полилинию Polyline в geos::LineString 
struct Polyline { 
std::vector<GeoP...
14 
Проблемы с производительностью 
! А если много полигонов? 
! И каждый состоит из множества точек? 
! И они постоянно о...
15 
А можно ли по-другому ? 
Хотим писать так (псевдокод): 
USE_MY_POINT_AS_YOUR_OWN(GeoPoint); 
USE_MY_LINESTRING_AS_YOUR...
16 
Нам нужна магия generic! 
Boost.Geometry?
17 
Расстояние между двумя точками 
struct Point { 
double x; 
double y; 
}; 
namespace boost::geometry { 
double distance...
18 
distance простая, но не generic 
! Работает только с конкретным типом Point 
! Работает только в двухмерном пространст...
19 
Добавляем шаблоны 
! Можно использовать разные типы точек, но только те, 
где есть public поля x и y 
! Остальные проб...
20 
Надо сделать generic access 
Хотим, чтобы выглядело так: 
namespace boost::geometry { 
template <typename P1, typename...
21 
Generic access 
// Добавляем access 
namespace boost::geometry::traits { 
template<typename P, int D> struct access {}...
22 
Применим к GeoPoint 
namespace 
boost::geometry::traits 
{ 
template<> 
struct 
access<GeoPoint, 
0> 
{ 
static 
doubl...
23 
О метафункции 
Дополнительно Boost накладывает ряд условий: 
! Тип-член ::type является результатом вычисления 
метафу...
24 
Обобщаем размерность 
// Метафункция в Boost.Geometry 
namespace boost::geometry { 
namespace traits { 
template<typen...
25 
Эволюция функции distance 
namespace boost::geometry { 
// Было 
template <typename P1, typename P2> 
inline double di...
26 
Рекурсивное инстанцирование 
template <typename P1, typename P2, int D> 
struct pythagoras { 
static double apply(cons...
27 
Промежуточные результаты 
! Можно использовать любой тип точки 
! Независимо от размерности пространства 
! Пока все е...
28 
Концепция Point (не финальная) 
! Класс traits::access, содержащий функцию get! 
! Специализация для класса traits::di...
29 
Адаптация GeoPoint для концепции точки 
namespace boost::geometry::traits { 
template<> struct dimension<GeoPoint> : b...
30 
Адаптация GeoPoint для концепции точки 
namespace boost::geometry::traits { 
template<> struct dimension<GeoPoint> : b...
31 
Концепция LineString (не финальная) 
По аналогии с точкой определим концепцию LineString 
! Совместимо с Boost.Range с...
32 
Адаптация Polyline 
namespace boost { 
template<> struct range_iterator<Polyline> { 
typedef std::vector<GeoPoint>::it...
33 
Что в итоге ? 
Для двух точек distance работает 
BOOST_GEOMETRY_REGISTER_POINT_2D(GeoPoint, double, cs::cartesian, lon...
34 
Что в итоге ? 
Для двух точек distance работает, а для двух геометрий разных 
типов — нет 
BOOST_GEOMETRY_REGISTER_POI...
35 
Добавляем геометрические примитивы 
Можно определить разные функции с именами, 
соответствующими передаваемым геометри...
36 
Добавляем геометрические примитивы 
Можно определить разные функции с именами, 
соответствующими передаваемым геометри...
37 
Добавляем геометрические примитивы 
Можно было бы определить разные перегрузки функции для 
разных пар геометрических ...
38 
Добавляем геометрические примитивы 
Можно было бы определить разные перегрузки функции для 
разных пар геометрических ...
39 
Есть 2 решения 
! SFINAE (Substitution Failure Is Not An Error) 
! Tag dispatching
40 
SFINAE 
Невозможность подстановки не является ошибкой 
template<typename T> 
typename T::type foo(T t) 
{ return T::ty...
41 
SFINAE 
! Использовалось в первой версии библиотеки 
! Имеет ряд ограничений 
template<typename T> 
struct bar { typed...
Tag dispatching 
! Tag dispatching – способ использования перегрузки функций 
для реализации концепций, при этом тэгом наз...
43 
Концепция Point (финальная) 
5 классов свойств сформировали концепцию «Точка»: 
! 4 метафункции 
– Специализация для к...
44 
Адаптация концепции точки (revisited) 
namespace boost::geometry::traits { 
template<> struct tag<GeoPoint> { typedef ...
45 
Tag dispatching by instance 
namespace boost::geometry { 
namespace dispatch { 
template<typename G1, typename G2> 
do...
46 
Хорошее решение. 
А можно ли еще лучше?
47 
Tag dispatching by type 
Заменим функции на структуры и соответствующие 
статические функции-члены 
namespace boost::g...
48 
Tag dispatching by type 
Нет необходимости создавать экземпляры тэгов 
namespace boost::geometry { 
template <typename...
49 
Возможности tag dispatching by type 
! Можно переопределять типы (coordinate_type и 
coordinate_system) 
! Можно перео...
50 
Добавляем геометрические 
примитивы 
Все геометрические примитивы состоят из точек, и, как 
следствие, наследуют ряд и...
Определим тип точки для геометрий 
51 
namespace boost::geometry { 
namespace traits { 
template<typename Geometry> struct...
52 
Переопределим dimension 
Для точки оставляем так, как было сделано ранее 
// Метафункция в Boost.Geometry 
namespace b...
53 
Переопределим dimension 
Переопределим dimension для других геометрических 
примитивов, используя dimension для точки ...
54 
Переопределим систему координат 
namespace boost::geometry { 
namespace traits { 
template<typename Point> struct coor...
55 
Переопределим тип координат 
namespace boost::geometry { 
namespace traits { 
template<typename Point> struct coordina...
56 
Обобщаем тип координат 
template <typename P1, typename P2, int D> 
struct pythagoras 
{ 
typedef typename select_most...
57 
Реверсивный порядок аргументов 
! Если 푖푑G2>푖푑퐺1, то надо обратить порядок аргументов 
! Явно указываем, что если 푖푑G2...
58 
Реверсивный порядок аргументов 
! Если G2, то надо обратить порядок аргументов 
! Явно указываем, что если G2, то не н...
59 
Изменяем distance 
namespace boost::geometry::dispatch { 
template<typename Geometry1, typename Geometry2, typename Ta...
60 
Изменяем distance 
Специализируем distance для прямого порядка аргументов 
namespace boost::geometry::dispatch { 
// P...
61 
Результаты
62 
Результаты 
Шаблонная магия сделала возможным использование своих 
структур данных с алгоритмами Boost.Geometry 
BOOST...
63 
Результаты 
! Малый объем дополнительного кода (несколько классов 
traits) 
! Практически нулевой overhead на производ...
64 
Вопросы?
65 
Спасибо за 
внимание!
66 
Алексей Чернигин 
Яндекс.Карты 
Разработчик 
achernigin@yandex-team. 
ru
Upcoming SlideShare
Loading in …5
×

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

864 views

Published on

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

Published in: Technology
  • Be the first to comment

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

  1. 1. 1
  2. 2. 2 карты Магия метапрограмирования. Boost.Geometry Алексей Чернигин
  3. 3. 3 Содержание ! Предыстория ! Почему именно generic ! Boost.Geometry – взгляд изнутри
  4. 4. 4 Предыстория
  5. 5. 5 Необходимые геометрические примитивы Точка Ломаная Прямоугольник Полигон Полигон с дырками
  6. 6. 6 Необходимые геометрические алгоритмы ! Пересечение двух геометрий ! Расстояние между двумя геометриями ! Взаимное расположение двух геометрий ! Площадь
  7. 7. 7 Требования на библиотеку При разработке мобильных приложений возникают следующие требования на используемые библиотеки и фреймворки: ! Кроссплатформенность ! Свободная лицензия ! Компактность ! Эффективность
  8. 8. 8 Существующие альтернативы ! CGAL ! GEOS ! Boost.Geometry (GGL – Generic Geometry Library)
  9. 9. 9 А что, если мы разрабатываем не приложение, а библиотеку?
  10. 10. 10 Требования на библиотеку Публичные заголовочные файлы не должны содержать типов из сторонних библиотек. В противном случае ! появляются дополнительные зависимости ! возникает необходимость контролировать используемые версии библиотек
  11. 11. 11 Суровая правда жизни, или как выглядит типичная интеграция со сторонней библиотекой…
  12. 12. 12 Пример интеграции с GEOS Конвертируем нашу точку Point в geos::Coordinate struct GeoPoint { double latitude; double longitude; }; const Coordinate pointToGeosCoordinate(const GeoPoint& point) { return Coordinate(point.longitude, point.latitude); }
  13. 13. 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); }
  14. 14. 14 Проблемы с производительностью ! А если много полигонов? ! И каждый состоит из множества точек? ! И они постоянно обновляются?
  15. 15. 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; }
  16. 16. 16 Нам нужна магия generic! Boost.Geometry?
  17. 17. 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); } };
  18. 18. 18 distance простая, но не generic ! Работает только с конкретным типом Point ! Работает только в двухмерном пространстве ! Работает только в декартовой системе координат ! Не работает с разными геометрическими примитивами (например, точка и отрезок) ! Точность всегда double
  19. 19. 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); }
  20. 20. 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); } }
  21. 21. 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); } }
  22. 22. 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; } }; }
  23. 23. 23 О метафункции Дополнительно Boost накладывает ряд условий: ! Тип-член ::type является результатом вычисления метафункции metafunction<Arg1, Arg2>::type ~ function(Arg1,Arg2) ! Тип-член ::value является результатом вычисления численной метафункции
  24. 24. 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>{};
  25. 25. 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)); } }
  26. 26. 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; } };
  27. 27. 27 Промежуточные результаты ! Можно использовать любой тип точки ! Независимо от размерности пространства ! Пока все еще нет независимости от системы координат – добавим соответствующие traits классы Проявляется концепция точки – таким же образом можно описывать концепции других примитивов
  28. 28. 28 Концепция Point (не финальная) ! Класс traits::access, содержащий функцию get! ! Специализация для класса traits::dimension ! Специализация для класса traits::coordinate_system ! Специализация для класса traits::coordinate_type
  29. 29. 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; } }; }
  30. 30. 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)
  31. 31. 31 Концепция LineString (не финальная) По аналогии с точкой определим концепцию LineString ! Совместимо с Boost.Range с произвольным доступом ! Специализация итераторов range_iterator и range_const_iterator! ! Переопределение функций range_begin и range_end! ! Тип, определяемый метафункцией range_value<…>::type, должен удовлетворять концепции Point
  32. 32. 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(); }
  33. 33. 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); }
  34. 34. 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); }
  35. 35. 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) {…}
  36. 36. 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) {…} Но мы хотим писать generic код 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);
  37. 37. 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);
  38. 38. 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); // ошибка - неоднозначность }
  39. 39. 39 Есть 2 решения ! SFINAE (Substitution Failure Is Not An Error) ! Tag dispatching
  40. 40. 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; }
  41. 41. 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’…
  42. 42. Tag dispatching ! Tag dispatching – способ использования перегрузки функций для реализации концепций, при этом тэгом называется пустая структура или класс. 42 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; }; } }
  43. 43. 43 Концепция Point (финальная) 5 классов свойств сформировали концепцию «Точка»: ! 4 метафункции – Специализация для класса traits::tag – Специализация для класса traits::coordinate_system – Специализация для класса traits::coordinate_type – Специализация для класса traits::dimension ! Класс traits::access, содержащий функцию get
  44. 44. 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)
  45. 45. 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); } }
  46. 46. 46 Хорошее решение. А можно ли еще лучше?
  47. 47. 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) { … } }; }
  48. 48. 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); } }
  49. 49. 49 Возможности tag dispatching by type ! Можно переопределять типы (coordinate_type и coordinate_system) ! Можно переопределять константы (dimension) ! Легко реализовать реверсивный порядок аргументов
  50. 50. 50 Добавляем геометрические примитивы Все геометрические примитивы состоят из точек, и, как следствие, наследуют ряд их свойств: ! размерность пространства; ! систему координат; ! тип координат (int, double и т.д.) Поэтому не хотим писать такие же классы свойств, как и для точки, для всех геометрических примитивов. Хотим использовать уже написанные для точки.
  51. 51. Определим тип точки для геометрий 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; }; }
  52. 52. 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>{};
  53. 53. 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> {};
  54. 54. 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; }; }
  55. 55. 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; }; }
  56. 56. 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); } };
  57. 57. 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> {}; }
  58. 58. 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 {}; }
  59. 59. 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); } };
  60. 60. 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) { … } }; }
  61. 61. 61 Результаты
  62. 62. 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); }
  63. 63. 63 Результаты ! Малый объем дополнительного кода (несколько классов traits) ! Практически нулевой overhead на производительность ! Абсолютно generic код, включая возможность обратного порядка аргументов
  64. 64. 64 Вопросы?
  65. 65. 65 Спасибо за внимание!
  66. 66. 66 Алексей Чернигин Яндекс.Карты Разработчик achernigin@yandex-team. ru

×