
Phil, That's certainly the most influential I've received against the idea of implementing the program as a stream interface. However, I think you and I prioritize our design decisions differently, as I'll explain below. I'm going to need to mull over this thread, and I'll take Paul Bristow's suggestion and put up all of the candidates under a new thread under the title "Request for views on how to present.." to get final thoughts on the matter before I make a decision. On 6/15/07, Phil Endecott <spam_from_boost_dev@chezphil.org> wrote:
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.)
I think if I were going this route (and I'm starting to see its merits, but more on that in a second), I would have all "set" functions that work in a chaining manner, and if I wanted to extend functionality as above, do as follows: void my_settings(svg_plot& plot) { my_plot . set_axis_color() . set_axis_line_width() . set_axis_line_style() // snip } 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.
Here is where we fundamentally disagree. It seems that you're saying (though you don't explicitly pass judgement, so I apologize if I take this the wrong way), that overloading the left shift operator for streams is a bad design because it does not stick with the de-facto standard class interface. However, when I switched from C to C++ in high school, (don't ask me why I started with C.. it wasn't an informed decision, and I certainly would have been better off learning it second ;D) I was struck by the syntax for the output and input. It was efficient and elegant compared to C calls (and compared to the boiled-down version above), everything that syntax should strive to be. Yes, I had to put in a little extra work with understanding underneath the hood how it worked, but I felt that it significantly aided my productivity once I got it. That was why I initially agreed with John Maddock's suggestion of providing it as a stream interface.. I felt that it made the learned user more productive, and the interface more easily extensible (though I forgot about this point until Joaquín Muñoz brought it up again) and elegant, at the cost of stumbling through an initially unfamiliar syntax. I agree with your statement that this isn't about looks. I will, however, split hairs and say that aesthetics do matter.. the difference between an iPod and, well, any other music player. Not that my header gets 5 stars in the interface department, and the << idea may be a miss, but I think experimenting with syntax in pursuit of that goal isn't completely misguided. As you show, the <<() operator doesn't just carry with it positive implications that I'd like it to have. 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?
I think that this question is the wrong question to ask. The real question in my playbook is "What's so special about your syntax that it solves your problem better than does the standard syntax?" I feel that being able to write, literally, my_plot << my_usual_settings << hide_axis(); is powerful stuff, and is more elegant than writing my_usual_settings( my_plot ); my_plot.hide_axis(); In summary:
- You don't need to overload operator<< to make your class extensible.
I don't know where my brain was earlier, but I agree with that. - Operator overloading makes the library harder to learn because what's
going on is less transparent.
I also agree. However, the operator overloading isn't purely a design based on my excessive malace towards the learning curve of my potential users :) Don't view this post as a rebuff against your ideas, as they are grounded on solid principles, worth consideration, and are backed up by what appears to be a majority consensus. But as for now, I think the best thing is to wait until this is a little further along so there's more to show (All of the proposed changes look like they could be done in a few hours with a few cups of coffee and some vinyl records blasting). Thank you for your time, Jake