[GTL] proposal for geometry set operations

The most useful algorithms implemented in the library are the polygon set operations. Also known as layer mask operations, Boolean operations, polygon clipping and merge etc. In the code uploaded to the vault the API for applying these algorithms is a little old fashioned and OO in nature. I have in mind a generic API. I define a polygon_set_concept, which is defined as anything that models a collection of polygon data, which could be a std::container of polygons, or rectangles, or a data structure that contains the internal representation used by my scanline algorithm that applies Boolean operations. (The polygon_set is so named because it is an argument to polygon set operations, not because it is somehow associated with std::set.) struct polygon_set_concept {...}; The polygon set concept provides the behaviors for objects that model polygon set. I define a polygon_set_traits, which must be specialized for user data types that model polygon set. template <> polygon_set_traits<std::list<UserPolygonType> > { ...minimal interface to satisfy concept... }; The user must register their type as a polygon set type: template <> geometry_traits<std::list<UserPolygonType> > { typedef polygon_set_concept geometry_concept; }; Then the user can use their type along with the library provided type to perform set operations through the intuitively overridden c++ logical operators & | ^ -. template <typename geometry_type_1, typename geometry_type_2> polygon_set_view operator&(const geometry_type_1& geo1, const geometry_type_2& geo2) { ...tag dispatching and so forth... } I also provide some Boolean logic capabilities to move flow control into functional code. template <typename geometry_type> polygon_set_view operator&(const geometry_type& geo, bool condition) { if(condition) return polygon_set_view(geo); } to the user to write code that looks like the following: std::list<UserPolygonType>* do_something_complex(bool condition1, bool condition2) { std::list<UserPolygonType>* resultPolygons = new std::list<UserPolygonType>; std::vector<rectangle_data> input1; initialize1(input1); std::vector<rectangle_data> input2; initialize2(input2); //if both condition1 and condition2 are true return the merged //result of the subtraction of input2 from input1 //stored as UserPolygonType into resultPolygons //if condition1 is true and condition2 is false return input1 //merged and stored as UserPolygonType into resultPolygons //if condition1 is false return empty resultPolygons resultPolygons |= ((input1 & condition1) - (input2 & condition2)); //legacy application puts onus on caller to delete resultPolygons return resultPolygons; } Note that I'm using a polygon_set_view to provide lazy evaluation of the scanline algorithm when chaining operations. A polygon_set_view models the polygon_set_concept on the lazily produced result of the operation. Because evaluation is lazy, the result of a Boolean operation should be stored to a polygon_set data structure before the arguments go out of scope or are deleted to ensure correct execution. Checking and proper generation of exceptions for such cases should be provided by the view. Modification of an argument after creating a view from it and before evaluating the view would be an undetectable user error. This proposed API is the most concise and intuitive way I can think of to provide these algorithms to the library user. Thoughts? Thanks, Luke

AMDG Simonson, Lucanus J wrote:
template <typename geometry_type_1, typename geometry_type_2> polygon_set_view operator&(const geometry_type_1& geo1, const geometry_type_2& geo2) { ...tag dispatching and so forth... }
I hope that this is not the actual declaration. Completely generic operator overloads are dangerous. In Christ, Steven Watanabe

Simonson, Lucanus J wrote:
template <typename geometry_type_1, typename geometry_type_2> polygon_set_view operator&(const geometry_type_1& geo1, const geometry_type_2& geo2) { ...tag dispatching and so forth... }
Steven Watanabe wrote: I hope that this is not the actual declaration. Completely generic operator overloads are dangerous.
There is no declaration yet, since I haven't finalized the design. I was thinking that it could be solved by registering the geometry types for which operator overloading is safe as operator args in the geometry_traits. template <> geometry_traits<UserPolygonSet> { typedef polygon_set_concept geometry_concept; typedef UserPolygonSet operator_arg; }; ... template <typename geometry_type_1, typename geometry_type_2> polygon_set_view operator&( typename geometry_traits<geometry_type_1>::operator_arg const& geo1, typename geometry_traits<geometry_type_2>::operator_arg const& geo2); Would this proposed solution address your concern about safety? Is there another way to make it safe that you would suggest instead? Thanks, Luke

On Thu, May 8, 2008 at 8:47 AM, Simonson, Lucanus J <lucanus.j.simonson@intel.com> wrote:
Simonson, Lucanus J wrote:
template <typename geometry_type_1, typename geometry_type_2> polygon_set_view operator&(const geometry_type_1& geo1, const geometry_type_2& geo2) { ...tag dispatching and so forth... }
Steven Watanabe wrote:
I hope that this is not the actual declaration. Completely generic operator overloads are dangerous.
There is no declaration yet, since I haven't finalized the design.
I was thinking that it could be solved by registering the geometry types for which operator overloading is safe as operator args in the geometry_traits.
template <> geometry_traits<UserPolygonSet> {
typedef polygon_set_concept geometry_concept; typedef UserPolygonSet operator_arg; };
...
template <typename geometry_type_1, typename geometry_type_2> polygon_set_view operator&( typename geometry_traits<geometry_type_1>::operator_arg const& geo1, typename geometry_traits<geometry_type_2>::operator_arg const& geo2);
This can't work: geometry_type_{1,2} are in a non deducible context. You need to SFINAE on an is_polygon_set trait. It can still break if the user class happens to have an operator& with different behaviour. I think you'll have an hard time using operators on user classes without explicitly stating that those operators are concept requirements, i.e. the user has to explicitly define them for their classes. Of course your library may provide shortcuts help define them more easily. -- gpd

template <> geometry_traits<UserPolygonSet> {
typedef polygon_set_concept geometry_concept; typedef UserPolygonSet operator_arg; };
...
template <typename geometry_type_1, typename geometry_type_2> polygon_set_view operator&( typename geometry_traits<geometry_type_1>::operator_arg const& geo1, typename geometry_traits<geometry_type_2>::operator_arg const& geo2);
Giovanni wrote: This can't work: geometry_type_{1,2} are in a non deducible context. You need to SFINAE on an is_polygon_set trait. It can still break if the user class happens to have an operator& with different behaviour. I think you'll have an hard time using operators on user classes without explicitly stating that those operators are concept requirements, i.e. the user has to explicitly define them for their classes. Of course your library may provide shortcuts help define them more easily.
Oh yes, of course. How about this, keep the registration with geometry_traits, but put the use of it on the return type: template < typename geometry_type_1, typename geometry_type_2> polygon_set_view<typename geometry_traits<geometry_type_1>::operator_arg>, typename geometry_traits<geometry_type_2>::operator_arg, op_and> operator&(geometry_type_1 const& geo1, geometry_type_2 const& geo2); Since the polygon_set_view needs to be templated in terms of the arguments anyway, the indirection provides the opportunity for SFINAE to prevent conflicts with operators for types that haven't been registered for use with the gtl operators. See correctly compiling code example below for full details. If the user has an operator & with different behavior that does cause them a problem I would expect them to not provide the trait that allows the type to be used with operator syntax. That type would result in substitution failure. I also can't use the operators internally in any circumstance where the arguments may be user types since I can't rely on them being registered. I don't want to require the user to define the operators because that would lead to too much code to adapt the library to their type system. It also leaves users who have already defined the operator for a different purpose unable to use the library with that type at all, whereas there would be an alternative syntax for types that can't be used with the operators. Thanks for pointing out my error, Luke #include <iostream> template <typename T> struct A { }; struct B {}; struct C {}; struct D {}; template<> struct A<B> { typedef B operator_arg; }; template<> struct A<C> { typedef C operator_arg; }; template <typename T1, typename T2> struct operator_result { const T1& t1; const T2& t2; operator_result(const T1& t1_in, const T2& t2_in): t1(t1_in), t2(t2_in) {} void print() {std::cout << "hello world\n"; } }; template <typename T1, typename T2> operator_result<typename A<T1>::operator_arg, typename A<T2>::operator_arg> operator&(const T1& lvalue, const T2& rvalue) { return operator_result<typename A<T1>::operator_arg, typename A<T2>::operator_arg>(lvalue, rvalue); } int main() { B b; C c; (b & c).print(); D d; //the next line does not compile //because D is not registered with A //(d & b).print(); return 0; }

Hi Luke, I'm only now having some time to follow the GTL evolution...
The user must register their type as a polygon set type:
template <> geometry_traits<std::list<UserPolygonType> > { typedef polygon_set_concept geometry_concept; };
This is probably explained somewhere but, could you state what a geometry_traits<T>::geometry_concept is?
Then the user can use their type along with the library provided type to perform set operations through the intuitively overridden c++ logical operators & | ^ -.
template <typename geometry_type_1, typename geometry_type_2> polygon_set_view operator&(const geometry_type_1& geo1, const geometry_type_2& geo2) { ...tag dispatching and so forth... }
tag dispatching (used in this way) won't prevent ambiguity in the overload resolution, so I don't think you should have operators taking just any types.
I also provide some Boolean logic capabilities to move flow control into functional code.
template <typename geometry_type> polygon_set_view operator&(const geometry_type& geo, bool condition) { if(condition) return polygon_set_view(geo); }
else return polygon_set_view(); I presume?
to the user to write code that looks like the following:
std::list<UserPolygonType>* do_something_complex(bool condition1, bool condition2) { std::list<UserPolygonType>* resultPolygons = new std::list<UserPolygonType>; std::vector<rectangle_data> input1; initialize1(input1); std::vector<rectangle_data> input2; initialize2(input2);
//if both condition1 and condition2 are true return the merged //result of the subtraction of input2 from input1 //stored as UserPolygonType into resultPolygons //if condition1 is true and condition2 is false return input1 //merged and stored as UserPolygonType into resultPolygons //if condition1 is false return empty resultPolygons resultPolygons |= ((input1 & condition1) - (input2 & condition2)); //legacy application puts onus on caller to delete resultPolygons return resultPolygons; }
Apart from the fact that the operator declaration needs sme work to avoid ambiguity, this is nice.
Note that I'm using a polygon_set_view to provide lazy evaluation of the scanline algorithm when chaining operations. A polygon_set_view models the polygon_set_concept on the lazily produced result of the operation.
I would like better if the library used a general expression templates framework instead of a custom mechanism so it can be used in much more than just polygon set operations. But then of course, does boost has any of that already?
Because evaluation is lazy, the result of a Boolean operation should be stored to a polygon_set data structure before the arguments go out of scope or are deleted to ensure correct execution.
Checking and proper generation of exceptions for such cases should be provided by the view. Modification of an argument after creating a view from it and before evaluating the view would be an undetectable user error.
How is this ensured in your proposal? I'm inclined to think that the only reliable way to do this is to store the operands via shared_ptr.
This proposed API is the most concise and intuitive way I can think of to provide these algorithms to the library user.
Do you mean the operator syntax or the lazy evaluation? I don't care for the former, but I certainly do for the latter, and to a broader extent. Best Fernando Cacciola

Fernando, Please take a look at my post with subject "operator syntax proposal checked into sandbox" in which some of your questions are answered. I'll answer the questions you wont' find answers to in that post below:'
The user must register their type as a polygon set type:
template <> geometry_traits<std::list<UserPolygonType> > { typedef polygon_set_concept geometry_concept; };
This is probably explained somewhere but, could you state what a
geometry_traits<T>::geometry_concept
is?
I would like better if the library used a general expression templates framework instead of a custom mechanism so it can be used in much more
just polygon set operations. But then of course, does boost has any of
It is a struct that declares all functions that relate specifically to a geometry concept. The struct is not templated so it makes a convenient tag. In the static functions of the struct the first argument is usually the templated geometry type. In this way the geometry concept can be used as both a namespace and a tag for resolving which function to call depending on what conceptual type the user's object has. The need for registration of a type can probably be dispensed with using something like enable-if, but this means I'm not allowed (by the compiler) to provide a default traits because that would not work with SFINAE. Right now all type identification is explicit and I use default traits for my own types. I'm still putting some thought into this on how it could be improved. than that
already?
I'm not sure what you mean here.
This proposed API is the most concise and intuitive way I can think of to provide these algorithms to the library user.
Do you mean the operator syntax or the lazy evaluation? I don't care for the former, but I certainly do for the latter, and to a broader extent.
I meant the operator syntax. The lazy evaluation is currently implemented as computing the full result of an operation only when it is used, but could be pushed further to have lazy iterator algorithms that produce the next increment of a result of an operation only when required. Whether there is advantage in doing so, however, is currently unclear. Thanks, Luke

Hi Luke,
Fernando,
Please take a look at my post with subject "operator syntax proposal checked into sandbox" in which some of your questions are answered.
I'll answer the questions you wont' find answers to in that post below:'
The user must register their type as a polygon set type:
template <> geometry_traits<std::list<UserPolygonType> > { typedef polygon_set_concept geometry_concept; };
This is probably explained somewhere but, could you state what a
geometry_traits<T>::geometry_concept
is?
It is a struct that declares all functions that relate specifically to a geometry concept.
Sorry, I meant to as what a "geometry concept" is? at a entirely conceptual level. I ask becasue I can't infer the meaning from the name (to it feels as it where "lucanus_concept" :)), nor from the particular polygon_set_concept example.
I would like better if the library used a general expression templates framework instead of a custom mechanism so it can be used in much more than just polygon set operations. But then of course, does boost has any of that already?
I'm not sure what you mean here.
I meant that lazy evaluation is the job of expression templates and that can (and should IMO) be intrumented by an *external* general framework for that purpose, like the pioneer PETE (http://acts.nersc.gov/pete/). IOW, this shouldn't by just an ad-hoc mechanism within some portions of your library if possible.
This proposed API is the most concise and intuitive way I can think of to provide these algorithms to the library user.
Do you mean the operator syntax or the lazy evaluation? I don't care for the former, but I certainly do for the latter, and to a broader extent.
I meant the operator syntax. The lazy evaluation is currently implemented as computing the full result of an operation only when it is used,
Indeed, which with a right framework becomes a property of any computation, not just clipping.
but could be pushed further to have lazy iterator algorithms that produce the next increment of a result of an operation only when required. Whether there is advantage in doing so, however, is currently unclear.
Right, I woudn't aim that far up now. Best -- Fernando Cacciola SciSoft http://scisoft-consulting.com http://fcacciola.50webs.com http://groups.google.com/group/cppba

This is probably explained somewhere but, could you state what a
geometry_traits<T>::geometry_concept
is?
Luke wrote:
It is a struct that declares all functions that relate specifically to a geometry concept.
Fernando wrote: Sorry, I meant to as what a "geometry concept" is? at a entirely conceptual level. I ask becasue I can't infer the meaning from the name (to it feels as it where "lucanus_concept" :)), nor from the particular
Fernando wrote: polygon_set_concept
example.
I meant that lazy evaluation is the job of expression templates and
(and should IMO) be intrumented by an *external* general framework for
geometry_traits<T> is a metafunction for looking up the conceptual type of a geometry type T. If you pass a type which is a point type the meta function should return point_concept, if you pass a type which is a polygon type the meta function should return polygon_concept. geometry_concept is just the generic name of the return value, which is why it feels generic. In this way the geometry concept is a tag, but it is also a namespace for functions to be declared in and may eventually provide concept checking capabilities, including using those check to ensure that the arguments to the functions which should model the concept do so correctly. From that standpoint I could break it up into three different things, a tag struct, a namespace for holding functions related to the concept and a concept struct that provides concept checking capabilities. On the other hand, I really like that the tag is the namespace for passing as a template parameter and it makes sense to me to provide concept checking eventually in the following manner: struct point_concept { template <typename T> struct provides_get<T> { typedef typename result_of<point_traits<T>::get> type; }; ... template <typename T> provides_get<T> get(const T& t, orientation_2d orient) { return point_traits<T>::get(t, orient); } }; Do you think it would be better to change: geometry_traits<T>::geometry_concept to: geometry_concept<T>::type to make it more obviously a metafunction? I think I may have chosen a misleading name for it, which led to confusion. Fernando wrote: that >can that
purpose, like the pioneer PETE (http://acts.nersc.gov/pete/). IOW, this shouldn't by just an ad-hoc mechanism within some portions of your library if possible.
I'm not surprised to see that other people have implemented the same basic idea. This PETE library is exactly the solution to a problem that a colleague of mine told me he was unable to solve with C++ when I explained my expression template to him and he saw that it solves it. I suppose a generic solution for expression templates could be devised that allows a functor type to be passed as the third parameter of the template, but it would also need to add a fourth template parameter for the type for the result that needs to be allocated and passed by reference into the functor to store the result. Alternately, the operation could be required to provide a lazy iterator output semantic. At that point, however, it is more generic than most people need and I fear that if such a library existed it wouldn't be very used. This is what I have in mind: template <typename l_type, typename r_type, typename functor_type, typename result_type> class lazy_evaluation { private: mutable result_type result_; mutable bool evaluated_; const l_type& lvalue_; const r_type& rvalue_; public: lazy_evaluation(const l_type& lvalue, const r_type& rvalue) : lvalue_(lvalue), rvalue_(rvalue), evaluate_(false) {} //needs to be declared const so callable from a //const reference to this const result_type& evaluate() const { //prevent redundant evaluation if(!evaluated_) { //modifies mutable result, which stores the //result of evaluation functor_type()(result_, lvalue_.evaluate(), rvalue_evaluate()); //modifies mutable evaluated member data evaluated_ = true; } return result_; } }; which factors out the expression template from my usage of it. It might make sense for me to refactor my code in this way to provide the abstraction between functor and expression template to eliminate specialization of the expression template. It may also prove useful when I implement mixed operations between axis-parallel and arbitrary angle geometry types in which the algorithms and operators selected will depend on the conceptual types of the input operands and I will need to apply the same pattern several times over. I'll need to think about it more. Thanks, Luke

This proposed API is the most concise and intuitive way I can think of to provide these algorithms to the library user.
Fernando wrote: Do you mean the operator syntax or the lazy evaluation? I don't care for the former, but I certainly do for the latter, and to a broader extent.
If you like lazy evaluation take a look at the lazy iterators I implemented for converting a range of geometry objects (such as polygons with holes) to a range of vertex data structures (vertex, half edge pairs) in iterator_geometry_to_set.h just checked into the sandbox under the gtl project. It is also a nice demonstration of how the geometry concept becomes useful as a template parameter for making algorithms generic. Thanks, Luke
participants (5)
-
Fernando Cacciola
-
Fernando Cacciola
-
Giovanni Piero Deretta
-
Simonson, Lucanus J
-
Steven Watanabe