|
Boost : |
From: Fernando Cacciola (fernando.cacciola_at_[hidden])
Date: 2008-05-02 09:52:20
Hi Luke,
> Please feel free to critique the code. I want to make sure I've got
> everything right before I rewrite the library.
In an eralier post you menioned that your interface was better than
requiring a point model to have member functions x() and y().
That's absolutely true, but the conclusion is that such a concept definition
is not generic so it's the concept itself which is wrong, not the use of
concepts (you know this of course).
We would never propose such a concept precisely because you can't use
concrete types *as is*.
The fundamental idea of generic programming is the use of "external"
adpatation. To that effect, your concept definition is way better than one
requiring members like x() or y() and I also noticed in the attached sample
code that there are no wrappers: this is very good and fundamental since
having to create an instance of a point_traits<T> passing a concrete point
type wouldn't be in the spirit of good generic programming and we would
complain.
Still, there are some points to discuss.
Take this for instance:
template <typename T>
class point_traits {
public:
typedef typename T::coordinate_type coordinate_type;
static inline coordinate_type get(const T& point, orientation_2d orient) {
return point.get(orient); }
static inline void set(T& point, orientation_2d orient, coordinate_type
value) {
point.set(orient, value); }
static inline T construct(coordinate_type x_value, coordinate_type
y_value) {
return T(x_value, y_value); }
};
No doubt this is generic as intended, but IMO it is a bit monolthic and
forces an unnnecesary verbose syntax.
I would rather have point_traits define types but not functions:
template <typename T>
struct point_traits {
typedef typename T::coordinate_type coordinate_type;
}
becasue this separation gives you more lattitud to decide how to define the
functions.
For example, you can have:
struct point_concept
{
template<class T>
typename point_traits<T>::coordinate_type
static inline get( T const& point, orientation_2d orient )
{ return point.get(orient); }
} ;
which users can specialize for concrete types just as in your case.
The difference becomes apparent in the user side syntax:
You can type this;
point_concept::get(point1, HORIZONTAL)
instead of:
point_traits<T>::get(point1, HORIZONTAL)
which can be quite significant when T is syntactically much more than just
'T'.
Now of course I would have free functions instead:
template<class T>
typename point_traits<T>::coordinate_type
static inline get_component( T const& point, orientation_2d orient )
{ return point.get(orient); }
Because free functions allows you to refactor your concept hierarchy with
minimal impact on the end user. Let me explain this:
On an upcoming dicussion we would have to argue about the concepts
themselves (in the abstract, regardless of the specific C++ interface).
Questions like:
Does a point has coordinates or is there a coordinate-free point concept
separatedly from the one with coordinates?
Does the library includes vectors as well?
Since, from a certain POV, both vectors and points have coordinates, should
there be a "cartesian" concept which is nothing but a tuple of components?
What about dimensions? Is a certain point concept for 2D? Is there a
separate point concept for 3D?
Such a discussion would affect the very definition of the existing concepts
and would result in a refactoring of the concepts design.
Now, say you haven't yet considered any of the above so you just define your
point concept as you do now. This fixes the following interface:
point_concept::get(point1, HORIZONTAL)
which ties the operation to extract a component of a point with a *specific*
concept via the nesting of the function in a class that corresponds to that
particular concept.
Now say that you want to add vectors to the library, and vectors have x,y
components as well: what do you do *now*?
You could create yet another accessor with essentially the same method
vector_concept::get(vector1, HORIZONTAL)
but then users would have to specialize both point_concept and
vector_concept even if their concrete class is the same for both.
Or perhaps you define a new concept, say cartesian, and deprecate
point_concept::get in favor of cartesian::get (or even break compatibility
if you are like me and never bargain on refactoring, specially these days
when method renaming is a snap in most development enviroments)
Free functions OTOH gives you much more flexibility to evolve the design
because users that were calling
get_component(q,orient)
won't have to change that call even if there are now new concepts for which
point is just a refinement.
Of course the discussion between using free functions instead of member
functions is not particular of generic programming but much more general.
Yet it is particularly important in generic programming because users have
to specialize the functions that instrument the concepts interface and free
functions are totally independent hence the most flexible.
Granted, in a perfectly tied up and "closed" design, this wouldn't matter at
all, but in the case of your library I think this flexibility is very
important because in the current form the library is not too general so
chances are it will stretch and be refactor over time as other domains are
added to it.
Best
-- Fernando Cacciola SciSoft http://scisoft-consulting.com http://fcacciola.50webs.com http://groups.google.com/group/cppba
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk