Proposal/InterestCheck: Boost.Geom

Hi people, Boost.Geom purpose is to provide a unified, zero-cost and pretty interface around existing geometric primitive implementations (for now only points and rectangles in 2D and 3D). I have uploaded the code+doc+tests in geom-0.1.zip in the vault. * The rest of this mail gives an idea about the rational behind the library * If you still have time, the manual contains a chapter `Rational' (http://geom.000hosted.com/html/c55.html) that explains the library purpose in detail (please read that before answering) * If you want to test it, the library is header-only and doesn't not depend on anything else (not even other parts of boost), you only need to add an include directory (see the manual chapters `Installation' and `Kick Start') Rational (summary) -------- For illustrative purposes, say you are working with Qt (QPoint and QRect): geom::point<QPoint> pt; // A QPoint instance behind a geom::point interface geom::box<QRect> rc; // A QRect instance behind a geom::box interface Now you can write code that uses pt and rc without having to worry about QPoint and QRect. Further more, if later you port your code to, say, WxWidgets (WxPoint and wxRect), all you have to do is to change the above two lines to: geom::point<wxPoint> pt; // Now using wxPoint internally geom::box<wxRect> rc; // Now using wxRect internally And the rest of the code remains the same (since the interface presented by geom::point<> and geom::box<> remains the same). Key points * Using QPoint directly is NOT faster than using geom::point<QPoint>. * Using geom::point<QPoint> makes the rest of your code independent of QPoint (so that you can easily switch to another library later), * geom::point/geom::box provide a richer interface than most existing points and rectangles implementations. * The library provides it's own implementation types (that you can plug into geom::point and geom::box) so you can test it even if you don't use Qt, WxWidgets.. etc I'll be happy to hear your opinion, Links: (If you are reading this mail long after the date it was written (26 Jan 2009) these links might be broken, checkout the library archive in the vault instead) - Full manual http://geom.000hosted.com/html/index.html - Rational chapter http://geom.000hosted.com/html/c55.html - Library archive (code+doc+tests) (int the vault) http://www.boostpro.com/vault/index.php?action=downloadfile&filename=geom-0.1.zip&directory=& Reguards, -- Anis

Mathias Gaunard wrote: preview this month or start of February. It is fairly complete now and revised by input from the list again. Question, related to this. Until now we didn't use the name "boost" on it or use namespace "boost" inside, just because it is not accepted or reviewed. We only state "proposed to boost" on the website. I've seen several libraries, and this one as well, immediately using the tag :boost" at first gauging of interest. What is the policy there? Are we encouraged to do this as well or is it better to wait if / until it is accepted? Barend

on Mon Jan 26 2009, Barend Gehrels <barend-AT-geodan.nl> wrote:
Thank you very much for being so responsible.
If you want, you can use "Boost" as long as you add a loud disclaimer stating that it's not an accepted library. However, I'm neither encouraging nor discouraging that practice. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

Anis Benyelloul wrote:
Hi Anis, Some quick thoughts: - Thank you for writing some documentation. I found the link unreliable at first but I was able to see the docs eventually. If anyone else failed to connect to the server I suggest trying again. - Perhaps you would like to compare and contrast your library vs. all the other proposals. - As far as I can see, you're supporting homogeneous co-ordinate types (i.e. I can't use a different type for altitude than for latitude/longitude), and you don't provide a way to iterate over all the co-ordinates of a point. I'm not saying this is a good or a bad thing, but it is one of the ways in which the libraries differ. - As I've said before, a variety of conflicting approaches are possible at this "bottom level" of a geometry library. I don't think any one approach is going to make all potential users happy. In order to gain momentum, a library will need to demonstrate that is has worthwhile features at higher levels (algorithms, containers, bindings etc.) If someone were to propose a library full of those higher-level features then it would become popular irrespective of its choice of point concepts. Until then we'll all propose lots of slightly-different "bottom levels" and disagree about which is best. - "Rationale" is spelt with an 'e' when it's a noun in this sense :-) Regards, Phil. P.S. Geometry challenge of the week: a container for lots of very short poly-lines that has less storage overhead than a (sorted) std::vector<std::vector<point>> and provides faster region lookup.

Phil Endecott <spam_from_boost_dev <at> chezphil.org> writes:
Hmm .. Initially I wasn't going to provide those links, but just kindly ask people to download the archive, unpack it and go to the right chapter manual. But then I though making it easier was worth it.
- Perhaps you would like to compare and contrast your library vs. all the other proposals.
Would be a good idea, indeed. I first need to check out the other libraries (in boost and others) before being able to make any meaningful comparison. But I can highlight the following points that I think might set things apart. My code: - Is oriented towards GUI programming not towards mathematical/geometrical/numerical computation. - Is zero cost: using a struct point { int x, y;} is not more efficient (speed&memory) then a geom::point<>; - Is not ``yet another points&rectangles library'' (like wxPoint/wxRect, QPoint/QRect, POINT/RECT, XPoint/XRect, SDL_Rect,.. etc) but rather an aim at providing an interface into which to plug implementation types.
- and you don't provide a way to iterate over all the co-ordinates of a point.
Currently I don't support anything beyond 2D and 3D (i.e the number of coordinates in always known in advance). Cases where the number of coordinates isn't known at compile time seems out of my scope,... but I'm not sure...
a library will need to demonstrate that is has worthwhile features at higher levels (algorithms, containers, bindings etc.)
Hmmm ... again this is beyond my initial scope.
Until then we'll all propose lots of slightly-different "bottom levels" and disagree about which is best.
I see...
- "Rationale" is spelt with an 'e' when it's a noun in this sense Ouch! ;) Thanks for pointing this out!
Reguards,

Anis Benyelloul wrote:
I gather that by pretty you mean . operator syntax for calling member functions of objects? For what it's worth I agree with you that dot operator syntax is prettier, but there are some drawbacks to the use of member vs. nonmember implementations. I have read your documentation briefly and looked at your point.hpp and point_traits.hpp as well as several other files. I work in the VSLI CAD and VSLI manufacturing process modeling application domains. In my domain practically every object is a rectangle, we work with points extensively, and object work with rectilinear polygons. For me the data I work on is very similar to your GUI rectangles and points, except rather than manipulating handfuls of rectangles I have billions of them. Aside from that, the rationale you describe for your library is exactly the same as my own rationale for my generic geometry library. Your approach is also very similar to my original approach. The problem I wanted to solve is that rather than GUI frameworks we have CAD tool frameworks that define their own rectangles, points, polygons etc. and code written for one is not portable to another. I solved that, then I solved it again, and again, and again. Now Dave has suggested an even nicer solution still, and I'll probably solve it yet again before boostcon. Barend is also on his third or fourth iteration with his geometry library.
I have uploaded the code+doc+tests in geom-0.1.zip in the vault.
There is a Math-Geometry directory in the vault where you will find quite a few similar submissions, including my original submission, gtl. You might want to consider moving yours to make it easier to find.
Your template point<> has a private member variable m_impl of point type T and allows the user to get a reference to that member by calling impl(). Why then make m_impl private? You seem to have in mind that people should unwrap an object in this manner. Do you also intend people to wrap a point_type by casting it to point<point_type>? I find your point_impl abstrction rather confusing. It seems like you are allowing a point<point<point_type> > with this construct. I don't think it is neccesary for it to be a friend of point<>, because you can simply call the public impl() function of point<>. Your traits seem a little heavy, with all supported operations on point<> being defined as a trait, why not just require that the user specialize point<> itself and provide a default that expects them to conform to some interface? In any case, I suggest you look at my library in the vault, Barend's library, Brandon's library, and my library in the sandbox (which is a rewrite of my original vault submission.) You will be able to find all of these by reading the boost dev. list archive searching for "geom". I think you will find we all solve the same problem at the "lowest level", but Barend, Brandon and myself also provide quite a bit of algorithmic meat on the bones of the generic interfaces to geometry objects. I hope you enjoy reading the past discussions from the boost dev list archives. I know that finding people who are intersed in solving the same types of problems I am working on has been a positive experience for me. Regards, Luke

Simonson, Lucanus J <lucanus.j.simonson <at> intel.com> writes:
I gather that by pretty you mean . operator syntax for calling member functions of objects?
By ``pretty'' I mean these kind of things: box<..> bx; bx.center() = point(0,0); // this will ``move'' the box so that it's center is // at the origin bx.corner<xmin, ymin, zmin>()=bx.center() // this will stretch the box so that // the corner is where the center was before In other words, the interface allows more complicated operations then simple access to the members. Instead of ``pretty' you could say ``rich'' or ``richer than a strcut point {int x,y;};''
I have read your documentation briefly and looked at your point.hpp and point_traits.hpp as well as several other files.
Thanks.
We have CAD tool frameworks that define their own rectangles, points, polygons etc. and code written for one is not portable to another.
Exactly. So I'm not alone, glad to hear it :)
You might want to consider moving yours to make it easier to find. Done.
Well, this is just to maintain the ``all public members are methods''-consistency thing. But you could make it public.
You seem to have in mind that people should unwrap an object in this manner.
Yes, this is very important when the time comes to pass your object as a parameter to the underlying framework.
Do you also intend people to wrap a point_type by casting it to point<point_type>?
Yes. There is a geom::point_wrap() and a geom::box_wrap() non-member functions that do that. Maybe I should mention them earlier in the manual.
It seems like you are allowing a point<point<point_type> > with this construct.
Yes, this not explained until somewhere deep inside chapter 5 or so. But this is not very important from a user perspective. This mainly allows to cut down the number of necessary member overloads of geom::point<>.
I don't think it is neccesary for it to be a friend of point<>, because you can simply call the public impl() function of point<>.
Your traits seem a little heavy,
Yes, but it is necessary to allow type-specific optimizations (such as if you are packing the X and Y coordinates in the same machine word, geom::point<>::operator==() will be able to compare both coordinates with the same operation, instead of the generic x1==x2 && y1==y2). On the other hand if you don't want to specialize everything (and are happy with the generic implementations) you can use point_xy_trait/point_xyz_traits which will specialize point_traits for you based on a subset of its features.
In any case, I suggest you look at my library in the vault,
Sure! Reguards,

Anis Benyelloul wrote:
I have wondered about this particular optimisation before and your mention made me investigate. gcc for ARM seems to make the optimisation but surprisingly the x86 version doesn't. Test code: struct pair { short a; short b; }; int compare_pair(struct pair p1, struct pair p2) { return p1.a==p2.a && p1.b==p2.b; } int compare_int(int i1, int i2) { return i1==i2; } Is there anything in the language that makes this sort of thing difficult for the compiler? I don't think so. Phil.

Anis Benyelloul wrote:
This is very clever, does center() generate an object that casts to point, but caches a reference to the box and implements an assignment operator that modifies box? What an interesting idea. I'm a little concerned that such a thing might be a little too clever. Without your comment I would assume that bx.center() returns a point by value, that the assignment of the origin point to that value modifies the temp copy and not the original box, and that the temp copy then goes out of scope. If reading a code leads a developer who is unfamiliar with the details of the library to the wrong impression of what it does, I fear it may indicate an unintuitive interface that could be error prone. Why not have two center() functions, one that returns a point by value, and one that accepts a point and returns that point by value. point pt = bx.center(); //assigns copy of center of box to pt bx.center(point(0,0)); //centers box on origin Point pt2 = bx.center(get_some_point(from_some_object)); //centers box on a point and stores a copy of the point to pt2 This API for dealing with the center of a box follows the principle of least astonishment.
Instead of ``pretty' you could say ``rich'' or ``richer than a strcut point {int x,y;};''
Rich seems like a less subjective term than pretty, since it implies quantity.
Exactly. So I'm not alone, glad to hear it :)
Actually, GUIs was the only other domain Dave A. could think of where my original proposal for rectilinear geometry library was applicable. I knew you had to be out there somewhere. I think there are many other domains that like to simplify their lives by using axis parallel geometries, but now I have arbitrary angle APIs in my library too, which covers everybody else.
My understanding is that doing so is not legal according to the C++ standard. Someone please correct me if I am wrong. struct A {}; struct B { A a; void bar(); }; void foo() { A a; B b; B& B_view_of_A = *((B*)&(a)); //result of cast is undefined, but generally works because B is the same in memory as A is most compilers B_view_of_A.bar(); } I know that you want your dot operator syntax because it is how you are used to working with geometry objects, but you can provide a rich API without dot operator syntax template <typename T> boost::enable_if<is_box_like<T>::type, point>::type center(T& obj); This definition of center can accept an A directly without need of wrapping it.
I had a similar problem with my wrappers in my original submission. I got around it by making point conform to the default expectation of the point_traits so that you could recursively call traits until you got down to the base level. Since there are no wrappers in the concepts based implementation I don't have such a problem anymore.
I overlooked that you provide such helper traits, but I had that in mind as one solution. It should not be necessary to use traits to provide type specific optimizations. You can specialize any member function of a templated class, however, you cannot partially specialize such a function, so you have to explicitly specify all template parameters, which can be somewhat limiting. #include <iostream> template <typename T> struct A { void foo() { std::cout << "default\n"; } }; template <> void A<int>::foo() { std::cout << "optimized for int\n"; } int main() { A<float> af; A<int> ai; af.foo(); ai.foo(); return 0; } Outputs: default optimized for int This is somewhat more direct than providing for the ability to use a user defined trait for every behavior of a geometry object, which requires quite a lot of typing and indirection within the library. I want to add that inlined indirection can be zero cost, but in practice the more layers of inlined functions you have the harder it gets for the compiler to optimize. Eventually the compiler bails on inlining and will fail to inline where you think it ought to. There is a huge improvement in the performance of my original library (which is similar to yours) when moving from gcc 3.x to gcc 4.x, primarily due to better inlining (we believe.) This undermines the claim of zero cost abstraction, because if it got cheaper it clearly wasn't zero cost in the first place. We did a lot of analysis, and in real code the compiler often failed to inline the simple accessor functions that it always inlined in toy test cases. This led to a double digit performance loss. By playing with pragmas and inline limit flags we recovered that performance, but I fear your faith in the compiler my be somewhat misplaced. Regards, Luke

Simonson, Lucanus J <lucanus.j.simonson <at> intel.com> writes:
I could have done it that way, but I didn't want to pay for an extra temporary object. So what it does is to return a ref to the _box itself_ (i.e `this') but wrapped behind a point interface. In fact, this uses the same mechanism that lets you wrap a QPoint/wxPoint/XPOINT/POINT... behind a geom::point<> (but see below). The corner() mechanism is also implemented this way.
I would assume that bx.center() returns a point by value,
Yeah, but what about this: point pt; pt.x()=10; // ???? Well, for simple properties like X, Y, Z most people will read the above line as ``assign 10 to the X property of pt''. What I wanted to do is to keep the consistency and be able to write: box bx; bx.center()=pt; // Assign pt to the `center' property of box. All the code revolves around this idea of abstractions and properties.
Rich seems like a less subjective term than pretty, since it implies quantity. Yes
I'm not sure about this either... I thought since those are non-polymorphic types, they have to be compatible with C, and C doesn't allow a compiler to add any funny pointer to virtual tables or such to structs....
Wrapping is not needed when you want to pass agruments to the library. POINT pt1; geom::point<POINT> pt2; pt2=pt1; // Assign geom::point = POINT works Wrapping is needed when you have a POINT a you need to work with it behind a geom::point<>. It is of course possible to copy to a geom::point and then copy the results back...
What I do is to first unwrap a type such as point < point < point_impl > > to get the point_impl inside, then I pass that to point_traits.
Yeah, but my idea was to keep the ``user customizable parts'' apart from the non-``user cutomizable parts'' of the library... but that's an idea.
We did a lot of analysis, and in real code the compiler often failed to inline the simple accessor functions that it always inlined in toy test cases.
Yes, I understand. PS. I hear lots of people in this thread talking about their own ideas in this domain but I find it hard to find each person's proposition/code (latest versions, and documentation when available), if everyone could provide links to their work it would be great. Reguards,

Hi Anis,
We currently use concepts, and have a set of metafunctions to enable any geometry object being used or modified by the library. So we say now, internally: geometry::set<0>(p, 3) and its x-value will get an 3 geometry::set_indexed<min_corner,0>(b, 3) and the x-vale of the lowerleft corner of the box will get a 3 The point or box involved can have any structure, for example: struct pnt {double x,y; } struct box1 {int left,top,right,bottom; } struct box2 {pnt lowerleft, pnt upperright; } This is internally. Of course the user can use the point directly, so can state: pnt p; p.x = 3; box1 b; b.left = 3; But the library uses an indirect way to be independant on the types choosen by the user.
Currently our library can be found at: http://geometrylibrary.geodan.nl We will post a new, substantially revised, version in one or two weeks and use Boost SVN as its new repository then. Regards, Barend

Barend Gehrels <barend <at> geodan.nl> writes:
Yeah, but my rationale was that you don't want to do this. My initial goal was to make my code independent of the underlying geometric primitives frameworks provide. It seems that you don't address this, do you?
Currently our library can be found at: http://geometrylibrary.geodan.nl
I've taken a quick look, and here are some my impressions (correct me if I'm wrong): - You provide your own implementation of points, boxes, circles... - You provide lots of geometric algorithms over those - Through some template specializations it is possible to use (say) QPoints with your algorithms. BUT I would still be manipulating QPoints directly in the rest of my code. - Is a box implemented always as two points? Reguards,

Hi Anis,
Sure we address this, it was in the piece that you snipped :-) The code is independant on the underlying geometries. We provide them but you can use your own.
Those are not the only ones possible. For the version published, users can use their own , custom, points as well. For the new version, that applies for all geometries. The new version doesn't include them by default. So you're encouraged to use your own. So this works, for example: int a[2] = {1,1}; int b[2] = {2,2}; double d = geometry::distance(a, b); No wrapper at all. That is implemented through metafunctions somewhere else.
Don't know exactly what you mean. It is possible to use QPoints, yes. And you can manipulate them directly, yes. Or you can manipulate them indirectly, whatever you prefer. If you're building a library piece you probably want the indirect approach. If you've a piece of user code, you probably find the direct approach more readable.
- Is a box implemented always as two points?
No. It can be left,right,top,bottom as well. Or left,right,top,bottom,front,back for a 3D box. There is a traits version giving access to the individual coordinates. However, that cannot yet be seen, it will be there soon. So this works as well: int box[4] = {1,1,10,10}; int c[2] = {5, 5}; bool is_inside = geometry::within(c, box); Regards, Barend

Barend Gehrels <barend <at> geodan.nl> writes:
My purpose is to make the indirect approach as/more readable and convenient as the direct one so that the user's won't have to tie their code to the underlying framework's points and boxes (i.e they won't have to deal directly with QPoints at all). It seems that your purpose is rather: Provide lots of algorithms and make them applicable to any type the user is currently working with (the user will still have to know/use QPoints before passing them to the algotithms). Or maybe I missed something?

Anis Benyelloul wrote:
When I first talked about geometry on this list I felt much the same as you (Anis) about this particular choice. Others (including Luke) felt that providing a library that users could apply to their "legacy" code was important. I think I have now come around to that point of view: a boost geometry library shouldn't try to be a "master library" that hides all other libraries' interfaces, but rather it should be an "equal player" that can inter-operate with other code in whatever style the user wants. My remaining concern about this is that it makes things more complicated: some of Luke's messages, for example, go way over my head! But as I said before, these details are less important than the question of functionality: once I've wrapped up my GUI library's points (etc), what can I actually *do* with them? Provide us with some compelling algorithms (etc) and then we'll go along with whatever stylistic decisions you have made. You asked about previous submissions. Barend and Luke replied; there has been one other recent one, by Brandon Kohn, announced here: http://thread.gmane.org/gmane.comp.lib.boost.devel/180334. This has the disadvantage compared to the other two of a lack of documentation. Cheers, Phil.

Phil Endecott wrote:
Well, all the library proposals provide the ability to be a rossetta-stone of type conversion between all existing legacy geometric data types. I'm not sure we abandoned the ability to be a master library. In particular we can still apply the concepts based API to legacy code, the difference is that now it happens so transparently that users may not even notice that the library also works with everyone elses data types. I'm also concerned that it makes things more complicated. I literally have to tip toe around compiler bugs in all three: gcc, icc and MSVC. ICC10 and earlier tries to instantiate operator templates for unnamed enum types instead of the built-in operator and throws syntax errors under certain circumstances of template meta-programming where it should SFINAE out. ICC goes to 11 now, which fixes the problem, but I need to support the older versions too... I worry that programming in this style is too hard. I really hope Bjarne succeeds in making it easier, but I fear it will be 5 years after the new standard is ratified before we get to the point where we were at with last year's compilers and their support (or lack thereof) of the 03 standard. I'll be 35 by then and ready to retire to management. I really resisted adding all the complexity, but once I figured out what I would get for the work I put in it wasn't so bad to actually do it. It is much harder to figure out why I need to use some metaprogramming tricks than to figure out how. Every metaprogramming problem has a metaprogramming solution, you just add a layer of abstraction. ;) Actually, if I didn't have the mantra "every metaprogramming problem has a metaprogramming solution" to keep me going I'd have probably given up porting to MSVC (which took two weeks) or providing generic operators that don't collide with std::operator+ on iterators etc. Regards, Luke

Barend Gehrels <barend <at> geodan.nl> writes:
It is probably possible to add such an approach on top of a library like ours.
I was just about to say the same thing about implementing `data-type independent'-algorithms on top of my proposal. All in all, I think the two approaches are orthogonal and solve two different problems (make user's code independent of the underlying geometry framework vs making algorithms applicable to any type the user is working with)

Anis Benyelloul wrote:
Not at all. The main difference between the two approaches is you implement your library functions as members of wrapper classes and we implement them as free functions. I made my algorithms independent of the underlying geometry framework. I had several different frameworks to content with and have integrated my library into all of them without taking on any dependencies. Its an integration model that demonstrates that C++ code can be much more modular than is commonly practiced. Just because we are ourselves the first users of our own libraries to write code that is independent of the underlying geometry frameworks doesn't mean that allowing other people to do so is not also our goal or somehow not accomplished by our libraries. If anything a user would benefit from the example of a few algorithms to help them craft the interfaces for their own. If we structure our header files correctly a user will be able to include just the generic interfaces for point and rectangle (and in my case interval, since the rectangle depends on it) and none of the advanced algorithms and use it to write geometric GUI code that is independent of the underlying GUI framework. Regards, Luke

Anis Benyelloul wrote:
We have to use the indirect approach to implement our library algorithms. We would like doing so to be as readable and convenient as possible so we aren't writing unreadable, inconvenient code.
Our goal is to do both. We want people to write their own generic algorithms instead of tying them to frameworks and legacy type systems, and we also want to provide high quality algorithms for generally useful operations that people shouldn't have to write for themselves. Your rectangle API is the following (comments added by me for discussion purposes): //does not initialize, depends on wrapped object's default constructor, which may not exist btw, and may not initialize //if you (or a user) inadvertently write code that depends on the unitialized value it will behave differently with different wrapped T types box() //there is a static assert in operator= so this is type checked which is good template<class Impl> box(const Impl& im){ this->operator=(im); } box(const box& im); //casts this to wrapped T BoxImpl& impl(); //casts this to const wrapped T const BoxImpl& impl() const; //your use of macros to define getters and setters saves you typing, but hurts readability get_* and set_* where star can be any x1, y1, z1, x2, y2, z2, xmin, ymin, zmin, xmax, ymax, zmax, width, height, depth //why do you provide get/set when you have the property acessors as well //isn't providing one or the other sufficient (more minimal) and better than providing both? Macro definitions of property accessors: bx.width()=bx.height()+3 value=bx.xmax()*bx.xmin() ... etc //I don't know what this does, are you assuming rectangles can be empty? bool valid() const; //you have static assert that Box specializes box_traits template<class Box> box& operator=(const Box& bx); box& operator=(const box& bx); //I'm guessing that this adds p.x to x1 and x2 and p.y to y1 and y2, but it could just as easily //add p.x to xmax and subtract p.x from xmin etc. template<class Point> box<typename traits_type::box_type> operator+(const Point& p) const; template<class Point> box<typename traits_type::box_type> operator-(const Point& p) const; //I'm guessing this clips the one box to the other... template<class Box> box<typename detail::box_ret_type<BoxImpl, Box>::type> operator&(const Box& bx) const; //I can't imagine what this does, perhaps grow this box to encompass bx? template<class Box> box<typename detail::box_ret_type<BoxImpl, Box>::type> operator|(const Box& bx) const; //you have static assert template<class Box> bool operator==(const Box& bx) const; template<class Box> bool operator!=(const Box& bx) const; template<class Point> box& operator+=(const Point& pt); template<class Point> box& operator-=(const Point& pt); template<class Box> box& operator&=(const Box& b); template<class Box> box& operator|=(const Box& b); /////// // Corner accessors template<x_corner xc, y_corner yc, z_corner zc> point<typename detail::corner_wrapper<impl_type, xc, yc, zc>::type>& corner(); template<x_corner xc, y_corner yc, z_corner zc> const point<typename detail::corner_wrapper<impl_type, xc, yc, zc>::type>& corner() const; /// you could have saved yourself typing by putting template<x_corner xc, y_corner yc, z_corner zc = z_corner_dummy> // above instead of repeating: template<x_corner xc, y_corner yc> point<typename detail::corner_wrapper<impl_type,xc,yc,z_corner_dummy>::type>& corner(); template<x_corner xc, y_corner yc> const point<typename detail::corner_wrapper<impl_type,xc,yc,z_corner_dummy>::type>& corner() const; //// // Center center_wrapper& center(); const center_wrapper& center() const; I wasn't able to find the static function you mentioned that casts BoxImpl to box<BoxImpl>, perhaps you meant that it auto casts by value through the constructor that takes T? What if you want to have a operator+ between two rectangles? Lets say it adds xmin to xmin, xmax to xmax etc, how would you do it without ambiguity with operator+ between rectangle and point? If you look in my vault gtl/RectangleImpl.h you will find that I defined quite a few more functions on my rectangle wrapper than you do on box. Imagine I were a user of your library and I wanted all these functions, how would I extend your library? Would I modify your source file to add their definitions to your box wrapper? What happens to me then when you release a new version of your library? What if I wanted to define my own operator| between any two rectangles, but it conflicts with yours, causing compile time or runtime errors where sometimes yours is called, sometimes mine, depending on whether the lhs is box<T> or not. I'd like to have the option of not including your operator functions, but still use your getters and setters (perhaps to implement by own operator|, for example.) Great, now I have to add isolating my own generic operators to my todo list.... Particularly when writing generic code (but for C++ in general) we prefer to implement functions as non-member non-friend and provide a minimal and sufficient interface on a class to allow it to encapsulate its invariant. Does box maintain the invariant that xmin <= xmax? If I set xmin to something greater than xmax, what happens, btw? From this viewpoint, these getters and setters should be the only interface to the class other than the constructors/assignment operators. I believe someone provided me a link to Sutter or someone comparable's article that discusses this, and Bjarne also talks about this issue when he gives his talks. Again, until you've experienced the problems this solves it might not make much sense. Recall that I'm telling you this but also originally did exactly the same thing you did only more so. I changed my mind about this based on more than just the say-so of the people who told me the same thing I'm telling you now. Regards, Luke

--------------------------------------------------
Sorry for not being more vocal ;). I've been lurking a bit. My library has been undergoing some fairly major changes including a few philosophical shifts... so while I've been working on documentation, it tends to be in a state of flux. I tend to be the programming type who likes to dive in and document later (and yes, perhaps that is a bad thing.. but so it is...) My latest efforts have shifted entirely away from algorithms and have just been in trying to define the "Unified Theory" of geometry type systems. I'll put this forward with documentation at such a time I think it's ready. If there is an interest in what I've been doing (please note: this is not a request for input or for anyone to take time out of their work/lives to look at my code :D) the code can be browsed at: http://gengeomalg.svn.sourceforge.net/viewvc/gengeomalg/ Cheers, Brandon

Mathias Gaunard wrote:
I agree, what about if an int[2] were also a 1D line segment? I'd have to say that the geometric concept modeled by int[] is ambiguous. Why not have int[4] be a quatrinary point? Someone in a university somewhere will be annoyed by the arbitrary choice of making it a rectangle. This is maybe a more obvious issue for me, since I have both an interval and point data types which are just class encapsulated arrays, but typesafe, so that you can't use an interval in an interface that accepts a point. I also have an interval_concept and generic interfaces on interval in addition to those for point. Finally, I have SFINAE protection on APIs that expect interval or point concept modeling objects. I leave the choice of whether int[2] is a point or an interval up to the user to specialize the geometry_concept metafunction. I use arrays all the time, especially in unit testing, because it is very concise to initialize them to constant values. I also like to loop over them and secretly hope that vectorizing compilers will someday come along that can actaully optimize those loops. Icc 11 is better than icc 10, but there is a long ways to go. By the way, Barend, how do you accomplish distinguishing between int[4] and int[2] statically? I'd like to see a code snippet to learn how that works. Thanks, Luke

Simonson, Lucanus J wrote: > Mathias Gaunard wrote: > >> Barend Gehrels wrote: >> >> >>> So this works as well: >>> int box[4] = {1,1,10,10}; >>> int c[2] = {5, 5}; >>> bool is_inside = geometry::within(c, box); >>> >> So an int[4] is a 2D box and an int[2] is a 2D point? >> That looks like quite some arbitrary choices... >> True. I said somewhere else: "That is implemented through metafunctions somewhere else". I didn't explain because there are some options there: - you can include a headerfile registering a c-array as a point - OR you can include a headerfile registering a c-array as a box (one of the two, there is a guard to avoid ambuigity). - OR a segment - alternatively you can implement manually traits classes to define that the c-array is like a point, or like a box, or a segment - alternatively you can call a macro which is just a macro-shortcut for the traits classes. There are a couple of them, for 2D point, 3D point, box, ... If the macro AND the header file are not provided you'll have to implement those traits classes of the fourth dash. > I agree, what about if an int[2] were also a 1D line segment? I'd have to say that the geometric concept modeled by int[] is ambiguous. Why not have int[4] be a quatrinary point? Sure, that is possible. > Someone in a university somewhere will be annoyed by the arbitrary choice of making it a rectangle. This is maybe a more obvious issue for me, since I have both an interval and point data types which are just class encapsulated arrays, but typesafe, so that you can't use an interval in an interface that accepts a point. I also have an interval_concept and generic interfaces on interval in addition to those for point. Finally, I have SFINAE protection on APIs that expect interval or point concept modeling objects. We left SFINAE, it worked but tag dispatching worked much better for us (thanks Dave!) > I leave the choice of whether int[2] is a point or an interval up to the user to specialize the geometry_concept metafunction. I think we have about the same approach there. > I use arrays all the time, especially in unit testing, because it is very concise to initialize them to constant values. I also like to loop over them and s > ecretly hope that vectorizing compilers will someday come along that can actaully optimize those loops. Icc 11 is better than icc 10, but there is a long ways to go. > OK. But arrays are not that fine in a std::vector. We've also a lot of unit tests, but as soon as I want to test a line or polygon, I have to comment or erase the array-case. So it is fine for the examples, you can have points and boxes of them, but in many cases it is probably not that useful... > By the way, Barend, how do you accomplish distinguishing between int[4] and int[2] statically? I'd like to see a code snippet to learn how that works. > I explained that a bit above, but I can always copy and paste a snippet. Following is from the headerfile you can use to register (= specialize) a any-sized c-array as a point (2D 3D 4D etc). By the way, Luke, I appreciate all your comments and discussions on the list! Here is the (Bruno's) snippet. I realize that this registers an any-sized c-array, for the example I originally wrote you'll have to (further) specialize [2] and [4] to a point and box namespace geometry { namespace traits { // specialize as being a POINT: template <typename T, size_t N> struct tag<T[N]> { typedef point_tag type; }; template <typename T, size_t N> struct coordinate_type<T[N]> { typedef T type; }; template <typename T, size_t N> struct dimension<T[N]>: boost::mpl::int_<N> {}; template <typename T, size_t N> struct access<T[N]> { template <size_t I> static inline T get(const T p[N]) { return p[I]; } template <size_t I> static inline void set(T p[N], const T& value) { p[I] = value; } }; }} There is also a coordinate system (in a separate headerfile). Regards, Barend

Barend Gehrels wrote:
We left SFINAE, it worked but tag dispatching worked much better for us (thanks Dave!)
I thought Dave's suggestion was to combine static assert with tag dispatching, but could also be accomplished by combining SFINAE with tag dispatching. The difference between the two would be error msgs and name collisions. Write a generic distance(T t1, T t2) function and try to compile with gcc 3.2.2 while using std::sort on a vector and you'll quickly see why static assert isn't always sufficient. Even with namespace protection around your function you can't be sure someone else won't define a generic function with the same and you can get a collision due to Koenig-lookup. I'm not sure I prefer the error that failure of static assert gives over "no function foo that accepts x y z" that you get with SFINAE. No function area() that accepts point_type is pretty much clear and descriptive enough for the user, and very concise. Of course, it doesn't help me when I make a metaprogramming mistake and a function won't instantiate as I intend, but I've always managed to figure out which function I intended to be called and debug why substitution failed. I recall you said you ran into compiler problems with SFINAE and switched to using static assert for that reason. I know I ran into my own compiler problems. I have to use SFINAE for generic operators, though, so I didn't find it too painful to extend it to cover the other functions once I was able to cover the operators. Moving the condition of the static assert to an enable if on the return value should be pretty easy since you've already implemented all the meta-logic.
Following is from the headerfile you can use to register (= specialize) a any-sized c-array as a point (2D 3D 4D etc).
Oh, I thought you were somehow detecting the presence of operator[] or something like that. I agree with you, however, that arrays aren't really that useful for geometric entities.
By the way, Luke, I appreciate all your comments and discussions on the list!
Thank you for saying so. I've really appreciated all the feedback I've gotten from boost developers, and from you. Using enable_if on traits turned out to be a real help to me in conditionally defining traits for refined concepts, and I think you were the one who showed me how that works. I'm still amazed that it works, because it looks like duplicate definitions since the specialization eventually resolves to be the same as the default. I'm looking forward to your next preview. Luke

Hi,
To make it clear for Boost users, it's the same approach as for the "adapted" stuff of Boost.Fusion, where by including for instance <boost/fusion/adapted/std_pair.hpp>, you *can* have pairs seen as a sequence. If you don't want to, just don't include that. It's a same principle in the Geometry Library. Bruno

Anis Benyelloul wrote:
This is the latest open source release of my legacy API originally proposed to boost about two years ago: http://www.boostpro.com/vault/index.php?PHPSESSID=a3fd72812c895e3b86676c9565a7adf6&direction=0&order=&directory=Math%20-%20Geometry : gtl.tar.gz This is the most recent version of my current API availabe to the community: https://svn.boost.org/svn/boost/sandbox/gtl/gtl/ I'd like to point out that the ideas contained in the sandbox implementation are not purely my own. Several members of the boost development community, as well as the other geometry library authors influenced the design and provided help with issues I faced such as how to conditionally provide a default traits with enable_if, how to deal with bugs in compilers etc. If you look at the mailing list archive, the development of the new gtl API is a long slow process of patient people helping me figure out for myself what they already knew from their own experience. I didn't understand the relative merits of different design considerations until I ran into the problems and realized that those were the problems the boost folks were solving with their approach. Until you've experienced the problem, the solution doesn't make much sense. Can you think of any problems with your current implementation that you would like to solve? Regards, Luke
participants (9)
-
Anis Benyelloul
-
Barend Gehrels
-
Brandon Kohn
-
Bruno Lalande
-
David Abrahams
-
Mathias Gaunard
-
Max
-
Phil Endecott
-
Simonson, Lucanus J