Алексей Чернигин — Магия метапрограммирования на...
-
Upload
yandex -
Category
Technology
-
view
496 -
download
2
description
Transcript of Алексей Чернигин — Магия метапрограммирования на...
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; double longitude; }; const Coordinate pointToGeosCoordinate(const GeoPoint& point) { return Coordinate(point.longitude, point.latitude); }
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
Проблемы с производительностью
! А если много полигонов? ! И каждый состоит из множества точек?
! И они постоянно обновляются?
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
Нам нужна магия generic! 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); } };
18
distance простая, но не generic
! Работает только с конкретным типом Point ! Работает только в двухмерном пространстве
! Работает только в декартовой системе координат ! Не работает с разными геометрическими примитивами
(например, точка и отрезок)
! Точность всегда double
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
Надо сделать 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
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
Применим к 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
О метафункции
Дополнительно Boost накладывает ряд условий: ! Тип-член ::type является результатом вычисления метафункции metafunction<Arg1, Arg2>::type ~ function(Arg1,Arg2)
! Тип-член ::value является результатом вычисления численной метафункции
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
Эволюция функции 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
Рекурсивное инстанцирование 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
Промежуточные результаты
! Можно использовать любой тип точки ! Независимо от размерности пространства
! Пока все еще нет независимости от системы координат – добавим соответствующие traits классы
Проявляется концепция точки – таким же образом можно описывать концепции других примитивов
28
Концепция Point (не финальная)
! Класс traits::access, содержащий функцию get!! Специализация для класса traits::dimension
! Специализация для класса traits::coordinate_system ! Специализация для класса traits::coordinate_type
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
Адаптация 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
Концепция LineString (не финальная)
По аналогии с точкой определим концепцию LineString ! Совместимо с Boost.Range с произвольным доступом
! Специализация итераторов range_iterator и range_const_iterator!
! Переопределение функций range_begin и range_end!! Тип, определяемый метафункцией range_value<…>::type, должен удовлетворять концепции Point
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
Что в итоге ?
Для двух точек 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
Что в итоге ?
Для двух точек 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
Добавляем геометрические примитивы Можно определить разные функции с именами, соответствующими передаваемым геометрическим примитивам 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
Добавляем геометрические примитивы Можно определить разные функции с именами, соответствующими передаваемым геометрическим примитивам 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 код
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
Добавляем геометрические примитивы
Можно было бы определить разные перегрузки функции для разных пар геометрических примитивов
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
Есть 2 решения
! SFINAE (Substitution Failure Is Not An Error) ! Tag dispatching
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
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
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; }; } }
43
Концепция Point (финальная)
5 классов свойств сформировали концепцию «Точка»: ! 4 метафункции
– Специализация для класса traits::tag – Специализация для класса traits::coordinate_system – Специализация для класса traits::coordinate_type – Специализация для класса traits::dimension ! Класс traits::access, содержащий функцию get
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
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
Хорошее решение. А можно ли еще лучше?
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
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
Возможности tag dispatching by type
! Можно переопределять типы (coordinate_type и coordinate_system)
! Можно переопределять константы (dimension)
! Легко реализовать реверсивный порядок аргументов
50
Добавляем геометрические примитивы
Все геометрические примитивы состоят из точек, и, как следствие, наследуют ряд их свойств: ! размерность пространства;
! систему координат; ! тип координат (int, double и т.д.)
Поэтому не хотим писать такие же классы свойств, как и для точки, для всех геометрических примитивов. Хотим использовать уже написанные для точки.
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
Переопределим 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
Переопределим 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
Переопределим систему координат 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
Переопределим тип координат 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
Обобщаем тип координат
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
Реверсивный порядок аргументов
! Если 𝑖𝑑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
Реверсивный порядок аргументов
! Если 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
Изменяем 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
Изменяем 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
Результаты
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
Результаты
! Малый объем дополнительного кода (несколько классов traits)
! Практически нулевой overhead на производительность
! Абсолютно generic код, включая возможность обратного порядка аргументов
64
Вопросы?
65
Спасибо за внимание!