budd@mist.CS.ORST.EDU (Tim Budd) (06/08/89)
Is anybody other than myself bothered by the fact that in an overloaded operator the treatment of the left and right arguments is not consistent? In particular, the class to examine is found, naturally, by examining the dynamic (run time) type of the left argument, whereas the disambiguation of the overloaded operator is found using the static (declared) type of the right argument. To illustrate, consider the following simple program. Although a is declared to be of type Foo (the static type) it actually contains a value of the derived type Bar (the dynamic type). When a is added to itself the method is found in class Bar (the dynamic type), but the version selected depends only on the declaration for a (the static type) and not its current value. This ``Bar,Foo'' is printed, not ``Bar,Bar''. --tim budd (budd@cs.orst.edu) class Bar; class Foo { public: virtual int operator+(Foo& x) { cout << "in Foo,Foo + \n"; return 7;} virtual int operator+(Bar& x) { cout << "in Foo,Bar + \n"; return 7;} }; class Bar : public Foo { public: int operator+(Foo& x) { cout << "in Bar,Foo + \n"; return 7;} int operator+(Bar& x) { cout << "in Bar,Bar + \n"; return 7;} }; main() { Foo *a; a = new Bar(); (*a) + (*a); }
vaughan@mcc.com (Paul Vaughan) (06/08/89)
Two questions: 1) Has anyone considered overloaded functions/operators that are selected not only by a match of parameter types, but also according to the return type needed in the context? That is, have multiple definitions for the same function name with differing return types. Forgive me if this has been beaten to death before. 2) In LISP Flavors and CLOS, there has been a movement away from "message passing" to generic functions. Using what is called multiple argument dispatch in CLOS, you get something like overloaded virtual functions. The functions can be "virtual" on each argument. One of the big advantages is that the distinction between member functions and other functions (overloaded or not) goes away. You no longer have a special syntax for member function invocation, pointers to member functions, or any of that, and it becomes almost transparent whether a data object is implemented as a class or not. Is anyone considering a similar movement for C++? Paul Vaughan, MCC CAD Program | ARPA: vaughan@mcc.com | Phone: [512] 338-3639 Box 200195, Austin, TX 78720 | UUCP: ...!cs.utexas.edu!milano!cadillac!vaughan
rfg@pink.ACA.MCC.COM (Ron Guilmette) (06/09/89)
In article <1100@cadillac.CAD.MCC.COM> vaughan@mcc.com (Paul Vaughan) writes: >Two questions: >1) >Has anyone considered overloaded functions/operators that are selected >not only by a match of parameter types, but also according to the >return type needed in the context? That is, have multiple >definitions for the same function name with differing return types. >Forgive me if this has been beaten to death before. Forgive me also! I have been meaning to ask about this for quite awhile now. I have heard that Bjarne had *some* good reason for not allowing overloading based on return types, but I never actually found out what that reason was. Perhaps Andrew or Bjarne will clarify this for us. If they do, I hope that they will not be shy of details. I really want to fully understand the reason for this choice, right down to the nitty-gritty compiler-implementation details. -- // Ron Guilmette - MCC - Experimental Systems Kit Project // 3500 West Balcones Center Drive, Austin, TX 78759 - (512)338-3740 // ARPA: rfg@mcc.com // UUCP: {rutgers,uunet,gatech,ames,pyramid}!cs.utexas.edu!pp!rfg
jima@hplsla.HP.COM (Jim Adcock) (06/09/89)
// Is anybody other than myself bothered by the fact that in an overloaded // operator the treatment of the left and right arguments is not consistent? // In particular, the class to examine is found, naturally, by examining the // dynamic (run time) type of the left argument, whereas the disambiguation // of the overloaded operator is found using the static (declared) type of the // right argument. //Don't worry, be happy. //To illustrate, consider the following simple program: #include <stream.h> class Bar; class Foo { public: virtual int reverseAdd(Foo& x) { cout << "in Foo,Foo + Foo\n"; return 7;} virtual int reverseAdd(Bar& x) { cout << "in Foo,Bar + Foo\n"; return 7;} virtual int operator+(Foo& x) { return x.reverseAdd(*this);} virtual int operator+(Bar& x) ; }; class Bar : public Foo { public: int reverseAdd(Foo& x) { cout << "in Bar, Foo + Bar\n"; return 7;} int reverseAdd(Bar& x) { cout << "in Bar, Bar + Bar\n"; return 7;} int operator+(Foo& x) { return x.reverseAdd(*this);} int operator+(Bar& x) { return x.reverseAdd(*this);} }; inline int Foo::operator+(Bar& x) { return x.reverseAdd(*this);} main() { Foo *a; a = new Bar(); (*a) + (*a); } // of course, if (a+b) == (b+a) in whatever system of math, // then you needn't reverse the order of the adds... // [...these examples aren't *really* in-line, of course...]
sakkinen@tukki.jyu.fi (Markku Sakkinen) (06/09/89)
In article <238@pink.ACA.MCC.COM> rfg@pink.aca.mcc.com.UUCP (Ron Guilmette) writes: >In article <1100@cadillac.CAD.MCC.COM> vaughan@mcc.com (Paul Vaughan) writes: >>Two questions: >>1) >>Has anyone considered overloaded functions/operators that are selected >>not only by a match of parameter types, but also according to the >>return type needed in the context? That is, have multiple >>definitions for the same function name with differing return types. >>Forgive me if this has been beaten to death before. > >Forgive me also! I have been meaning to ask about this for quite awhile >now. > >I have heard that Bjarne had *some* good reason for not allowing overloading >based on return types, but I never actually found out what that reason was. > >Perhaps Andrew or Bjarne will clarify this for us. If they do, I hope that >they will not be shy of details. I really want to fully understand the >reason for this choice, right down to the nitty-gritty compiler-implementation >details. I think there is a plausible fundamental reason, independent of compiler implementation (although omitting this kind of overloading obviously makes the compiler writer's job a little easier, too). In C++ expressions, the context of each subexpression (e.g. a function invocation) in general only weakly determines the expected type of the subexpression. This is in part caused by the automatic type conversions, of which there are too many to my personal liking. One example of these is the widening of char values to int in many contexts: it even prevents us from declaring an overloaded function pair corresponding to the _argument_ types char and int. The automatic type conversions would be most problematic in the following case: no overloaded function matches the expected type exactly, but more than one of them would qualify for automatic conversion. Say, you have functions returning short and unsigned but the context requires a long value - which one should the compiler choose? If you think you have a logical answer to this, let's make it a bit more difficult: the required value is of class type, say Pet, and the overloaded functions return values of other class types, say Dog and Cat, both of which can be automatically converted to Pet. Of course, there would be even situations where there is no cue at all to which overloaded alternative is appropriate, e.g. (void) juggle (a, b, c); If overloading based on return type were to be introduced, then compilers should issue at least warnings at every place where the choice is ambiguous. I am afraid that one would be obliged to use a lot of explicit type casts, and the convenience of the feature would thus sometimes be questionable. - However, there certainly are cases in which the facility would be very nice to have. Markku Sakkinen Department of Computer Science, University of Jyvaskyla (try to imagine umlauts on those a's)
ark@alice.UUCP (Andrew Koenig) (06/09/89)
In article <238@pink.ACA.MCC.COM>, rfg@pink.ACA.MCC.COM (Ron Guilmette) writes: > I have heard that Bjarne had *some* good reason for not allowing overloading > based on return types, but I never actually found out what that reason was. The reason is that overloading based on return types would mean abandoning the notion that an expression has a type. Right now in C and C++ it is possible to figure out how to evaluate an expression by doing a depth-first traversal of its parse tree: once you have evaluated all the descendants of a node and found their types, you have all the information you need to evaluate the node itself. If it were possible for the evaluation of a node to depend on the type of its ancestors, in general that would mean that you'd have to understand an entire expression to be able to evaluate any of it. That would make evaluation much harder to understand, both for the compiler and for human programmers. I would not be surprised to find expression evaluation to be NP-hard under those circumstances. -- --Andrew Koenig ark@europa.att.com
rfg@pink.ACA.MCC.COM (Ron Guilmette) (06/10/89)
In article <868@tukki.jyu.fi> markku@jytko.jyu.fi (Markku Sakkinen) SAKKINEN@FINJYU.bitnet (alternative) writes: >In article <238@pink.ACA.MCC.COM> rfg@pink.aca.mcc.com.UUCP (Ron Guilmette) writes: >>In article <1100@cadillac.CAD.MCC.COM> vaughan@mcc.com (Paul Vaughan) writes: >>>Two questions: >>>1) >>>Has anyone considered overloaded functions/operators that are selected >>>not only by a match of parameter types, but also according to the >>>return type ... >>Forgive me also! I have been meaning to ask about this for quite awhile >>now. >I think there is a plausible fundamental reason, independent of compiler >implementation (although omitting this kind of overloading obviously >makes the compiler writer's job a little easier, too). > >In C++ expressions, the context of each subexpression (e.g. a function >invocation) in general only weakly determines the expected type of >the subexpression. So far we are in agreement. As an example of just what you are talking about, assume that we have: typedef ... A; typedef ... B; class C { public: C () {} operator A () { ... } // type converter C => A operator B () { ... } // type converter C => B }; void func (A a); void func (B b); C c; ... func (c); Note that the ambiguous thing here is the *variable* "c". Note also that there are already rules in the language which state (exactly) what is supposed to happen in such cases. >This is in part caused by the automatic type conversions, >of which there are too many to my personal liking. What to obviously meant was "builtin" type conversions. I agree with what you said, but to be more precise, you would have to say that that the ambiguity problems are caused *all* forms of type conversions (both builtin and user-defined) which may be applied *implicitly*. >One example of these >is the widening of char values to int in many contexts: it even prevents us >from declaring an overloaded function pair corresponding to the _argument_ >types char and int. Who sez? I believe this is legal now. >The automatic type conversions would be most problematic in the following case: >no overloaded function matches the expected type exactly, but more than >one of them would qualify for automatic conversion. This is almost the same as the example I gave above except that in my example, the "ambiguous subexpression" is a variable and not a function call. Again, I have to reiterate, the language already has rules for such cases. What is there to prevent these rules from being extended to handle the additional (very similar) case(s) of functions overloaded by return types? In effect, such functions could simply be treated as if they *all* returned some "universal" type for which there were a set of implicit conversion operators declared, one for each possible return type for which an overloading of the given function exists. Then, further ambiguity resolution could take place as normal (i.e. as it would for my example above). Now this further ambiguity resolution might result in the compiler issuing an error (as I believe it would for my example above) because there is still too much ambiguity, but at least we could use our "functions-overloaded-by-return-types" in most contexts. That would be better than the current state of affairs where we are prevented from even declaring such overloadings. >Of course, there would be even situations where there is no cue at all to >which overloaded alternative is appropriate, e.g. > (void) juggle (a, b, c); In that case, as I say, you should allow the compiler to say "this is ambiguous" and issue an error to that effect. Of course the example you gave might not be ambiguous if one (and only one) of the "overloadings-of-juggle-based-on-return-types" had a return type for which there was a user-defined type conversion operator which could convert the given return type to a void type. >If overloading based on return type were to be introduced, >then compilers should issue at least warnings at every place where >the choice is ambiguous. "Ambiguous" is in the eye of the compiler/language. For cases where the standard disambiguation rules can be applied (and leave you with only one possible meaning) there should *not* be any errors or warnings issued. For cases where the attempt at disambiguation (within the language guidelines) still leaves you with multiple possible meanings, you should *always* get an error. >... I am afraid that one would be obliged to >use a lot of explicit type casts, and the convenience of the feature >would thus sometimes be questionable. Wrong! As for my example above, explicit casts would only be needed for cases which are *still* ambiguous after application of all available disambiguation rules. > However, there certainly are >cases in which the facility would be very nice to have. Right! -- // Ron Guilmette - MCC - Experimental Systems Kit Project // 3500 West Balcones Center Drive, Austin, TX 78759 - (512)338-3740 // ARPA: rfg@mcc.com // UUCP: {rutgers,uunet,gatech,ames,pyramid}!cs.utexas.edu!pp!rfg
budd@mist.CS.ORST.EDU (Tim Budd) (06/10/89)
The technique used by Jim Adcock in his response to my original question about the inconsistent handing of left and right arguments in overloaded opertors is what is known as multiple (or double) polymorphism. It was introduced by Dan Ingalls in a paper in the first OOPSLA conference, and deserves to be more widely known and used. (Actually, Adcock's idea is a very slight improvement on Ingalls - rather than encode the type of the receiver in the selector for the reverse message, i.e., addToInteger, addToFloat, etc, he encodes it as the overloaded type, i.e. addTo(Integer& x) addTo(Float& x). In practice I don't think this makes any difference). I was aware of this technique; the point of my note (well hidden) was to point out that you needed some trick like this, and that overloaded operators didn't solve the complete problem. Anyway, to get to the point; I've written a technical report describing two different generalized arithmetic packages in C++, comparing the efficiency of Smalltalk style coercive generality to double polymorphism. This should be back from the printers in a week or so. If you would like a copy send me a paper mail address. (Actually, if you can read LaTex I can e-mail the raw document). --tim budd, budd@cs.orst.edu
dlw@odi.com (Dan Weinreb) (06/11/89)
In article <9454@alice.UUCP> ark@alice.UUCP (Andrew Koenig) writes:
If it were possible for the evaluation of a node to depend on the
type of its ancestors, in general that would mean that you'd have
to understand an entire expression to be able to evaluate any
of it. That would make evaluation much harder to understand,
both for the compiler and for human programmers. I would not be
surprised to find expression evaluation to be NP-hard under
those circumstances.
Indeed. For confirmation, read any Ada compiler. Ada does allow
overloading on the returned value. This results in compilers of
unspeakable complexity. There are other things that make Ada compiler
complex, but it's easy to see the direct effects of overloaded
returned values on the complexity of the compiler. The need to
support overloading of returned values changes the entire modularity
and control structure of certain parts of the compiler into a real
nightmare. And it can make Ada programs hard to understand, when
there's a lot of overloading. In the long run, the decision to have
overloading only on arguments is a good one.
Dan Weinreb Object Design, Inc. dlw@odi.com
simpson@poseidon.uucp (Scott Simpson) (06/13/89)
In article <244@pink.ACA.MCC.COM> rfg@pink.aca.mcc.com.UUCP (Ron Guilmette) writes: >In article <868@tukki.jyu.fi> markku@jytko.jyu.fi (Markku Sakkinen) SAKKINEN@FINJYU.bitnet (alternative) writes: >>In article <238@pink.ACA.MCC.COM> rfg@pink.aca.mcc.com.UUCP (Ron Guilmette) writes: >>One example of these >>is the widening of char values to int in many contexts: it even prevents us >>from declaring an overloaded function pair corresponding to the _argument_ >>types char and int. > >Who sez? I believe this is legal now. Indeed it is. See section 8.4.1, page 236 in Stroustrup. There are a few lines like istream& operator>>(char&); istream& operator>>(short&); istream& operator>>(int&); Scott Simpson TRW Space and Defense Sector oberon!trwarcadia!simpson (UUCP) trwarcadia!simpson@usc.edu (Internet)
sakkinen@tukki.jyu.fi (Markku Sakkinen) (06/13/89)
The front end requires me to put here more new than included lines - let's try some extra blank lines. In article <244@pink.ACA.MCC.COM> rfg@pink.aca.mcc.com.UUCP (Ron Guilmette) writes: >In article <868@tukki.jyu.fi> markku@jytko.jyu.fi (Markku Sakkinen) SAKKINEN@FINJYU.bitnet (alternative) writes: >>In article <238@pink.ACA.MCC.COM> rfg@pink.aca.mcc.com.UUCP (Ron Guilmette) writes: >>>In article <1100@cadillac.CAD.MCC.COM> vaughan@mcc.com (Paul Vaughan) writes: >>>>Two questions: >>>>1) >>>>Has anyone considered overloaded functions/operators that are selected >>>>not only by a match of parameter types, but also according to the >>>>return type ... ... >>I think there is a plausible fundamental reason, independent of compiler >>implementation (although omitting this kind of overloading obviously >>makes the compiler writer's job a little easier, too). >> >>In C++ expressions, the context of each subexpression (e.g. a function >>invocation) in general only weakly determines the expected type of >>the subexpression. >So far we are in agreement. As an example of just what you are >talking about, assume that we have: > > typedef ... A; > typedef ... B; > class C { > public: > C () {} > operator A () { ... } // type converter C => A > operator B () { ... } // type converter C => B > }; > > void func (A a); > void func (B b); > C c; > ... > func (c); > >Note that the ambiguous thing here is the *variable* "c". Note also that >there are already rules in the language which state (exactly) what is supposed >to happen in such cases. This example is beside the point: it is about overloading based on _argument_ types, not _return_ type; we had no argument (no pun intended) about that. However, your last statement happens to be incorrect: in a case like this, the C++ compiler (release 1.2) signals "error: ambiguous argument for overloaded func()" Further: - There is nothing ambiguous about the variable c itself: it is of class C, and the ambiguity is only in the invocation of func. - The example would also need an explicit "overload func;" declaration, although Stroustrup currently seems to regret that. >>This is in part caused by the automatic type conversions, >>of which there are too many to my personal liking. >What to obviously meant was "builtin" type conversions. > >I agree with what you said, but to be more precise, you would have to say >that that the ambiguity problems are caused *all* forms of type >conversions (both builtin and user-defined) which may be applied >*implicitly*. That is rather exactly what I originally said, if you read more carefully. >>One example of these >>is the widening of char values to int in many contexts: it even prevents us >>from declaring an overloaded function pair corresponding to the _argument_ >>types char and int. >Who sez? I believe this is legal now. For instance, the aforementioned compiler sez: "warning: the overloading mechanism cannot tell a void (int ) from a void (char )" And subsection 6.6 of the C++ Reference Manual (at the end of Stroutrup's book) says: "First, any operands of type char, unsigned char, or short are converted to int." Knowing is better than believing. >>The automatic type conversions would be most problematic in the following case: >>no overloaded function matches the expected type exactly, but more than >>one of them would qualify for automatic conversion. >This is almost the same as the example I gave above except that in my example, >the "ambiguous subexpression" is a variable and not a function call. > >Again, I have to reiterate, the language already has rules for such cases. >What is there to prevent these rules from being extended to handle the >additional (very similar) case(s) of functions overloaded by return types? As proved above, the language does _not_ have such rules. And how could there be any sensible rule to decide automatically between A and B in the example? >In effect, such functions could simply be treated as if they *all* returned >some "universal" type for which there were a set of implicit conversion >operators declared, one for each possible return type for which an overloading >of the given function exists. Then, further ambiguity resolution could take >place as normal (i.e. as it would for my example above). Now this further >ambiguity resolution might result in the compiler issuing an error (as I >believe it would for my example above) because there is still too much >ambiguity, but at least we could use our "functions-overloaded-by-return-types" >in most contexts. That would be better than the current state of affairs >where we are prevented from even declaring such overloadings. The "universal type" idea does not look logical at all: the problem is to choose the "right" function in the first place, not to invoke _some_ function and then convert its result. >>Of course, there would be even situations where there is no cue at all to >>which overloaded alternative is appropriate, e.g. >> (void) juggle (a, b, c); >In that case, as I say, you should allow the compiler to say "this is >ambiguous" and issue an error to that effect. Here we agree completely. >Of course the example you gave might not be ambiguous if one (and only >one) of the "overloadings-of-juggle-based-on-return-types" had a return >type for which there was a user-defined type conversion operator >which could convert the given return type to a void type. Sorry, my example was not the best possible. Although that is the currently recommended idiom at least in C for stating, "I know the function yields a result but I want to discard it", in C++ it will indeed cause a conversion function to be invoked if the result type of juggle is a _class_ for which an "operator void ()" has been defined! Therefore, to get a situation without any cues we must leave out '(void)'. (I checked both cases.) >>If overloading based on return type were to be introduced, >>then compilers should issue at least warnings at every place where >>the choice is ambiguous. >"Ambiguous" is in the eye of the compiler/language. For cases where the >standard disambiguation rules can be applied (and leave you with only one >possible meaning) there should *not* be any errors or warnings issued. >For cases where the attempt at disambiguation (within the language >guidelines) still leaves you with multiple possible meanings, you >should *always* get an error. I don't think there was any disagreement directly on this point. Nevertheless, especially with regard to what you are saying below, you probably want the disambiguations / implicit conversions to cater for the largest possible portion of cases. I consider that to be dangerous because of new possibilities for undetected programming errors. (Already in my ECOOP'88 paper I argued against the principle of using one-argument constructors automatically as type conversion functions.) This is somewhat analogous to the problems and errors caused by simple-minded linearisation of multiple inheritance (see e.g. various papers of Alan Snyder). >>... I am afraid that one would be obliged to >>use a lot of explicit type casts, and the convenience of the feature >>would thus sometimes be questionable. >Wrong! As for my example above, explicit casts would only be needed >for cases which are *still* ambiguous after application of all available >disambiguation rules. It depends! It would certainly be _possible_ to program so that very few cases remain ambiguous, but it might be difficult. >> However, there certainly are >>cases in which the facility would be very nice to have. >Right! > >-- >// Ron Guilmette - MCC - Experimental Systems Kit Project >// 3500 West Balcones Center Drive, Austin, TX 78759 - (512)338-3740 >// ARPA: rfg@mcc.com >// UUCP: {rutgers,uunet,gatech,ames,pyramid}!cs.utexas.edu!pp!rfg In the meantime, I have already seen a couple of responses from compiler implementers: they anticipated great difficulties for the idea, so we are probably left with "wouldn't it be nice sometimes". Markku Sakkinen Department of Computer Science University of Jyvaskyla (a's with umlauts) Seminaarinkatu 15 SF-40640 Jyvaskyla (umlauts again) Finland
sakkinen@tukki.jyu.fi (Markku Sakkinen) (06/13/89)
In article <5023@wiley.UUCP> simpson@poseidon.UUCP (Scott Simpson) writes: -In article <244@pink.ACA.MCC.COM> rfg@pink.aca.mcc.com.UUCP (Ron Guilmette) writes: ->In article <868@tukki.jyu.fi> markku@jytko.jyu.fi (Markku Sakkinen) SAKKINEN@FINJYU.bitnet (alternative) writes: ->>In article <238@pink.ACA.MCC.COM> rfg@pink.aca.mcc.com.UUCP (Ron Guilmette) writes: ->>One example of these ->>is the widening of char values to int in many contexts: it even prevents us ->>from declaring an overloaded function pair corresponding to the _argument_ ->>types char and int. -> ->Who sez? I believe this is legal now. - -Indeed it is. See section 8.4.1, page 236 in Stroustrup. There are -a few lines like - - istream& operator>>(char&); - istream& operator>>(short&); - istream& operator>>(int&); - Scott Simpson - TRW Space and Defense Sector - oberon!trwarcadia!simpson (UUCP) - trwarcadia!simpson@usc.edu (Internet) This just came here when I had posted my previous (long) article. The trick is that this example has _reference_ arguments, which are almost the same as pointers. A pointer to char is definitely distinct from a pointer to int; this is clearly necessary for pointer arithmetic. If you look at subsection 8.2.1 (page 227) of Stroustrup, you can see (among others) the output operator and function ostream& operator<<(int) { ... } ostream& put(char&); Guess why there isn't ostream& operator<<(char); instead of the put function! Markku Sakkinen Department of Computer Science University of Jyvaskyla (a's with umlauts) Seminaarinkatu 15 SF-40100 Jyvaskyla (umlauts again) Finland
simpson@trwarcadia.uucp (Scott Simpson) (06/14/89)
In article <884@tukki.jyu.fi> markku@jytko.jyu.fi (Markku Sakkinen) SAKKINEN@FINJYU.bitnet (alternative) writes: >If you look at subsection 8.2.1 (page 227) of Stroustrup, >you can see (among others) the output operator and function > ostream& operator<<(int) { ... } > ostream& put(char&); >Guess why there isn't > ostream& operator<<(char); >instead of the put function! But in the GNU C++ library there is. If you give GNU C++ the -fchar-const flag, it will call overloaded functions using the char version of <<. The default is to automatically convert it to an int. Scott Simpson TRW Space and Defense Sector oberon!trwarcadia!simpson (UUCP) trwarcadia!simpson@usc.edu (Internet)
ark@alice.UUCP (Andrew Koenig) (06/14/89)
In article <5043@wiley.UUCP>, simpson@trwarcadia.uucp (Scott Simpson) writes: > In article <884@tukki.jyu.fi> markku@jytko.jyu.fi (Markku Sakkinen) SAKKINEN@FINJYU.bitnet (alternative) writes: > >If you look at subsection 8.2.1 (page 227) of Stroustrup, > >you can see (among others) the output operator and function > > ostream& operator<<(int) { ... } > > ostream& put(char&); > >Guess why there isn't > > ostream& operator<<(char); > >instead of the put function! > > But in the GNU C++ library there is. There is in C++ 2.0 as well. cout << 'a'; finally prints `a' instead of `97'. -- --Andrew Koenig ark@europa.att.com