Boost logo

Boost :

From: Phil Endecott (spam_from_boost_dev_at_[hidden])
Date: 2007-06-15 18:34:58


Jake Voytko wrote:
> p << image_size (800, 600)
> << show_axis << x_axis_color( blue ) << y_axis_color( darkgray )
> << x_axis_major_tick_color( gray) << y_axis_major_tick_color( darkgray )
> << x_axis_minor_tick_color( gray ) << y_axis_minor_tick_color( gray )
> << show_background_border << background_border_color ( darkgray )
> << background_border_thickness( 5 )
> << background_color ( lightblue )

I not at all keen on this. I'll try to explain why.

Consider any of the textbook examples of classes, and how they can (or
can't) be extended:

- I want something that's like std::string, but I'd really like to be
able to have
   void std::string::toLowerCase(void) { .... }

- I want something like textbook::animal, but I need
   bool textbook::animal::has_four_legs() { return num_legs()==4; }

- I want something like textbook::solidshape, but I need
   int num_holes_through_shape(void) { .... }

- I want something like mylibrary::Date, but I need
   dayofweek mylibrary::Date::day_that_month_started_on() { ..... }

So wanting to take a class and extending it is very common, and in the
cases of interest these extensions are all things that can be
implemented using the public interface of the existing class.

There are basically two ways in which it can be done:

1. Create your own subclass which inherits from the thing you're trying
to extend and adds the new methods. This gets a bit tricky if you want
to add multiple independent methods to the same base class, and any
methods in the base class that return things of their own type will
return the base class not the subclass. So it's probably not the right
thing to do in many cases.

2. Write free functions instead of methods. i.e. instead of writing
    if (the_animal.has_four_legs()) { ... }
    you have to write
    if (has_four_legs(the_animal)) { ... }
The only real problem with this way is the you have to remember whether
each 'method' is a real method of the base class or a free function.
One option for a class that expects to be extended is to put
*everything* in free functions, having a minimal set of
invariant-preserving public methods that access the private data. (You
can even do without them using friend declarations.)

Let's think about iostreams for a moment. There the need is to be able
to extend the ostream class with new methods for formatted output of
new types. Essentially what it does is:

ostream strm(fn);
int i;
char c;
std::string s;
somo_other_type z;
write(strm,i);
write(strm,c);
write(strm,s);
write(strm,z);

i.e. free functions for each extension, except that the library
designers chose not to use a conventional function name like 'write',
but instead chose operator<<. Is this a good choice? Well, there is a
famous quote somewhere (would anyone like to remind me who it was)
something like "well I gave up on C++ when I saw them doing a left
shift on the standard output". It's a jokey quote, but adding extra
"magic" syntax is something that makes what's really going on in the
program less transparent. It doesn't take at all long for someone to
learn that A.B(C) means "pass C as the parameter to method B of object
A" (and it helps that it is essentially the same in most OO
languages). But you need to get a lot further in to your textbook to
understand what is really going on when you write A<<B<<C.

Of course the advantage of operator overloading is that the code can be
a bit sorter. In the case of iostreams, the library is widely used and
early-learnt, and so some increased learning curve has been traded off
against saved keystrokes.

Now to your code:

> p << image_size (800, 600)
> << show_axis << x_axis_color( blue ) << y_axis_color( darkgray )
> << x_axis_major_tick_color( gray) << y_axis_major_tick_color( darkgray )
> << x_axis_minor_tick_color( gray ) << y_axis_minor_tick_color( gray )
> << show_background_border << background_border_color ( darkgray )
> << background_border_thickness( 5 )
> << background_color ( lightblue )

My first impression on seeing that is that this _is_ the iostreams
overloading of operator<<. For example, a library that rendered text
into a bitmap image might subclass ostream so that you could write:

bitmap_text_buffer b;
int i;
some_type_with_operator<<_defined X;
b << at(50,50) << "hello world" << X;

My second impression is that your class is some sort of non-iostream
stream and that each '<<something' is putting something into the
stream. (SVG is XML, so maybe each one is adding an element to the XML
output.) But no, on further inspection it isn't even doing that: the
plot class does not behave as a stream.

In fact you're using << purely as a way to save keystrokes. You're
just using A<<B(C) as shorthand for B(A,C). In fact it would actually
be less confusing to use a different operator:

       p % image_size (800, 600)
         % show_axis % x_axis_color( blue ) % y_axis_color( darkgray )
         % x_axis_major_tick_color( gray) % y_axis_major_tick_color(
darkgray )
         % x_axis_minor_tick_color( gray ) % y_axis_minor_tick_color(
gray )
         % show_background_border % background_border_color ( darkgray )
         % background_border_thickness( 5 )
         % background_color ( lightblue )

('operator,' anyone?)

So let me write it out in full, as I would probably implement it:

       image_size (p, 800, 600);
       show_axis(p);
       x_axis_color(p, blue );
       y_axis_color(p, darkgray );
       x_axis_major_tick_color(p, gray);
       y_axis_major_tick_color(p, darkgray );
       x_axis_minor_tick_color(p, gray );
       y_axis_minor_tick_color(p, gray );
       show_background_border(p);
       background_border_color (p, darkgray );
       background_border_thickness(p, 5 );
       background_color (p, lightblue );

Now what is so special about this particular library that that isn't
good enough?
(Please don't tell me that "it doesn't look as nice". Looking good is
not what programs should be judged by.)

In summary:

- You don't need to overload operator<< to make your class extensible.
- Operator overloading makes the library harder to learn because what's
going on is less transparent.

Regards,

Phil.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk