Boost logo

Boost :

From: Karl Nelson (kenelson_at_[hidden])
Date: 2001-12-20 18:14:36


> > Of course if it turns out there are other killer arguments in favor of the
> > string-maker approach, you should choose string-maker.
>
> I doubt I'll ever find killer arguments for string-builder vs
> manipulator.. at best, several vague inconveniences in the manipulator
> approach, but not much more.

Given that there are only vague inconveniences I personally think
the one which looks most familiar to the user is the best. We are basically
trading one set of inconviences for another.

> Because the problem with the manipulator approach, is it's impossible to
> make it behave exactly like a manipulator. It looks promising, with a
> few small issues.
> The problem, IMO, is you can never exterminate all the issues at the
> same time, whatever you try.

If the issues are as simple as "you must pass all arguments to the
format before the close of statement", I think that is acceptable.
I wish more manipulators did that because then I wouldn't get hex
print outs halfway accross my program when I don't want them.

 
> In current state, Karl Nelson's class has several 'minor'
> inconveniences, eg the issue on endf.
> My conjecture is there will always be minor issues with this approach,
> because intrinsequely it is not truly a manipulator, it's not really a
> stream concept.
>
>
> > But if it is even close to a toss-up, consider that the manipulator
> > approach many be easier to teach, easier to learn, and be viewed as the
> > more mainstream approach. I worry that we will all waste endless time
> > explaining why string-maker was chosen over manipulator.
>
> This 'better ease for teaching' is skin deep. In fact the 'manipulator
> approach' does not make format a real manipulator.
> Obviously, the data is held inside a format object, not the stream.
>

Some things are just better looking and thus more quickly understood.
<< means to the user "put this in". >> means "take this out".
% means what? A convient operator which we enslaved to make another
stream like concept.

> George A. Heintzelman's msg# 21946 phrased it better than I would be
able to.
>
> Whatever you do, it won't be a true manipulator (since everything is
> held inside ou object, not the stream)
>
> Whatever you do, it won't always act like a manipulator in _all_
> situations.
> The most extreme example was already given :
> cout << format("%2 %1");
> cout << x << y;
>
> The class will stop acting like a manipulator at some point before such
> an extreme example.
> depending on the design of the class, it can show up in natural usages,
> or not so natural.
> But sooner or later, it will.

All classes have some limitations. If fixing them makes the class
less likely to be used, then sometimes it is just better to
to just list the limitations and be done with it.

> So when you teach format's usage, you'll have to teach when, and how,
> the format class differs from a manipulator.
> And, depending on the design, this can be awfully hard to understand.
>
>
> Looking at Karl Nelson's ofrstream, you can get an idea of the rise in
> complexity needed to make format act somehow like a manipulator.
>
> Let's examine what is the 'manipulator approach'.
> (Karl, correct me if I'm wrong. The ofrstream.cc I have might be old,
> and I had read it in depth a long time ago, I might be mistaken now)

(was in the process of revising it when this thread caught my eye,
a contract took all my time between when I submitted it originally and
now.)

>
> string s ="%1"; //or whatever
> cout << format(s) << toto << endf << "continue";
>
> 1. format(s); constructs an object, which basically parses s, and
> prepares internal structures :
> storage for N strings, an internal stringstream (which format state is
> set equal to cout's)

This stage is common to both string building and manipulator approaches.

> 2. operator<<(stream&, const format&) returns a temporary proxy, of type
> 'ofrstream'.
> This temporary is passed shared pointers to access the internal
> structures of the initial object.
> 3. operator<<(ofrstream&, const T&) is called for ' << toto'
> it dumps toto the internal stringstream, possibly with some formatting
> options, and puts the result string into one of the prepared storage.
> It might format toto into a string 20 times, or zero, depending on the
> format string.
> 4. endf
> its type, ofr_end, is :
> typedef ostream& (*ofr_end)(ofrstream&);
>
> And the class defines
> ostream& ofrstream::operator<<(ofr_end func) {
> return func(*this);
> }
>
> So, when reaching endf, the ofrstream is told to dump all internal
> strings into cout. And cout is returned.

Excellent summary of how it works.

> The class would look more sexy without 'endf'.
> But it's completely needed as soon as you begin using more complicated
> constructs :
>
> cout << format(s) << toto << format("%1") << x;
>
> Ouch !!
> x goes into an ofrstream created by first format.
> The second format never receives its arguments.

Nested formats dump than reparse IIRC. Thus the above
would look like

  cout << format(s) << toto <<endf
       << format("%1") << x <<endf;

> Now, is this really better and less 'strange' than a simple,
> straightforward, gentle string-builder ??
>
> (I had read a note in another format implementation, that in a complex
> construct, the reference counting used to give the ownership of the
> internal structures to the proxy lead to problems. The described
> construct was highly unnatural, but it was yet another limitation to
> warn the user about. I'll try to find this in my archives..)

Yes, the use of streams to build of the strings is the only solution.
Holding the types (which I implemented the first time) was out
of the question. Temporaries resulting from conversions were
very problematic.

--Karl


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