Boost logo

Boost :

From: Samuel Krempp (krempp_at_[hidden])
Date: 2001-12-20 17:47:38


On Thu, 2001-12-20 at 16:57, Beman Dawes wrote:

> Because the "manipulator-like" approach looks so familiar, and the
> "string-maker" approach looks strange at first, that in of itself is an
> argument for the manipulator-like approach.

Les apparences peuvent etre trompeuses !
(='Looks can be deceiptive' -does this adjective exist, or did I just
invent it ? )

In fact, the string-maker approach is very simple. its functionaliy is
so much simpler to grasp than that of the 'manipulator-approach'.

string-builder :
if you forget about operator%, and only provide
str(const format&)
so users can take the string value,
Its functionality is perfectly clear. it's a sting-builder ;-)

Now, when people want to use their format obejct in the middle of stream
I/O chains, they'd do :
cout << str( format("%1 %2") << x << y ) << "bla bla";

That's the real, true, basic string-builder.
it's a bit annoying to use in the middle of streams, but it's pretty
clear.

At this point, either we leave our class like that,
or try to do without parenthesis.

Then, as I say in message 21960, since we need an operator for passing
arguments, we choose one whose precedence is higher than that of a
stream,
so the "passing arguments" scheme does not mix with the
surrounding stream '<<' scheme.
(I mean, it still works as if inside parenthesis. It gets arguments,
format them, dumps a string, and dies before the next stream '<<' is
called)

The line above becomes :
cout << format("%1 %2") % x % y << "bla bla";

But it's still pretty simple, we added this operator :

stream& operator<< (stream& os, const format& f) {
        return os << str(f);
}

but really it works just the same as the previous string builder.

> 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.

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.

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.

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.

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)

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)
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.

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.

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..)

regards

-- 
Samuel

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