[GTL] geometry type introspection

I've been working on an idea for using some compile time concept type introspection and tag dispatching to allow generically named free functions (such as contains()) to compile and work in user code when called with any sensible combination of geometry types. This should make user code much more concise than with my current design; all the concept related boilerplate can be dispensed with. //should compile bool result1 = contains(rectangle1, rectangle2); //should compile bool result2 = contains(rectangle1, point); //should compile bool result3 = contains(interval1, interval2); //should compile bool result4 = contains(interval1, coordinate_value); //should not compile because it is non-sensical bool result5 = contains(rectangle1, interval1); //should not compile because it is non-sensical bool result6 = contains(coordinate_value, point1); The idea is that a geometry_traits struct be specialized for each geometry data type such that it provides a typedef for the related geometry concept. That typedef can be inspected at compile time to provide type introspection and passed as a tag value for tag dispatching. See example code pasted below. The one problem with this scheme is that it doesn't allow a user type to model more than one geometry type. That means that if someone models point with std::pair<int, int> it makes it impossible for someone else to model interval with std::pair<int, int> anywhere where the declaration of the one for point is visible. I consider that to be a good thing because it enforces type safety on user types, since you lose the protection of the compiler from inadvertently using point data as interval data if you model std::pair<int, int> as both. However, people eager to use tuple for all of their applicable geometry data types should take note of this limitation. Do you guys prefer this scheme to what I showed in example3.cpp? Thanks, Luke //some point related code omitted struct rectangle_concept { template <typename rectangle_type, typename rectangle_type2> static bool contains_dispatch(const rectangle_type& rectangle, const rectangle_type2 rectangle_contained, rectangle_concept tag) { std::cout << "rectangle contains rectangle\n"; } template <typename rectangle_type, typename point_type> static bool contains_dispatch(const rectangle_type& rectangle, const point_type point_contained, point_concept tag) { std::cout << "rectangle contains point\n"; } template <typename rectangle_type, typename geometry_type> static bool contains(const rectangle_type& rectangle, const geometry_type& contained_geometry_object); }; struct no_type {}; template <typename T> struct geometry_traits { typedef no_type geometry_concept; }; template <> struct geometry_traits<point_data<int> > { typedef point_concept geometry_concept; }; template <> struct geometry_traits<rectangle_data<int> > { typedef rectangle_concept geometry_concept; }; template <typename rectangle_type, typename geometry_type> bool rectangle_concept::contains(const rectangle_type& rectangle, const geometry_type& contained_geometry_object) { //disambiguate function to call through tag dispatching return rectangle_concept::contains_dispatch(rectangle, contained_geometry_object, typename geometry_traits<geometry_type>::geometry_concept()); } //function that should copile for any two geometry types that may have a containment relationship template <typename geometry_type_1, typename geometry_type_2> bool contains(const geometry_type_1& geometry_object, const geometry_type_2& contained_geometry_object) { //select function to call through introspection typename geometry_traits<geometry_type_1>::geometry_concept concept_instantiation; return concept_instantiation.contains(geometry_object, contained_geometry_object); } void foo() { rectangle_data<int> rect1, rect2; point_data<int> point; rectangle_concept::contains(rect1, point); rectangle_concept::contains(rect1, rect2); contains(rect1, point); contains(rect1, rect2); } output from calling foo(): rectangle contains point rectangle contains rectangle rectangle contains point rectangle contains rectangle

AMDG Simonson, Lucanus J wrote:
I've been working on an idea for using some compile time concept type introspection and tag dispatching to allow generically named free functions (such as contains()) to compile and work in user code when called with any sensible combination of geometry types. This should make user code much more concise than with my current design; all the concept related boilerplate can be dispensed with. <snip>
Do you guys prefer this scheme to what I showed in example3.cpp?
I haven't looked at your code yet, but it feels sound. This is basically how fusion, MPL, &c. work, BTW. In Christ, Steven Watanabe

Simonson, Lucanus J wrote:
I've been working on an idea for using some compile time concept type introspection and tag dispatching to allow generically named free functions (such as contains()) to compile and work in user code when called with any sensible combination of geometry types. This should make user code much more concise than with my current design; all the concept related boilerplate can be dispensed with. Do you guys prefer this scheme to what I showed in example3.cpp?
Steven Watanabe replied: I haven't looked at your code yet, but it feels sound. This is basically how fusion, MPL, &c. work, BTW.
I thought of another problem with the tag dispatching scheme for making everything free functions. The difficulty was that often the return value of a function should depend upon the types passed into it. I solve this problem with SFINAE to overload, for example, the get() function to return a coordinate when called with a point type and to return an interval when called with a rectangle type. (The horizontal-axis component of a rectangle is an interval from low x to high x values.) This allows different coordinate types to be the return value when get is called on different objects of the same conceptual type and completely different types when the get function is called on dissimilar types. I added a return type to the geometry traits with a different name for each conceptual type and then look it up for the return value of overloaded versions of get. If it isn't specified in the specialization of geometry_traits for the given type that is substitution failure, and the compiler selects the other version of get. See code snippet below. Thanks, Luke struct no_type {}; template <typename T> struct geometry_traits { typedef no_type geometry_concept; }; template <> struct geometry_traits<point_data<int> > { typedef point_concept geometry_concept; typedef int point_get_retval; }; template <> struct geometry_traits<point_data<long long> > { typedef point_concept geometry_concept; typedef long long point_get_retval; }; template <> struct geometry_traits<rectangle_data<int> > { typedef rectangle_concept geometry_concept; typedef interval_data<int> rectangle_get_retval; }; template <> struct geometry_traits<rectangle_data<long long> > { typedef rectangle_concept geometry_concept; typedef interval_data<long long> rectangle_get_retval; }; template <typename point_type> typename geometry_traits<point_type>::point_get_retval get(const point_type& point, orientation_2d orient) { return point_concept::get(point, orient); } template <typename rectangle_type> typename geometry_traits<rectangle_type>::rectangle_get_retval get(const rectangle_type& rectangle, orientation_2d orient) { return rectangle_concept::get(rectangle, orient); } void foo() { get(point, HORIZONTAL); get(rect1, HORIZONTAL); get(rect3, HORIZONTAL); }
participants (2)
-
Simonson, Lucanus J
-
Steven Watanabe