[comp.lang.c++] Inputting enums

sdm@cs.brown.edu (Scott Meyers) (01/16/90)

Consider the following straightforward program for reading in enumerated
type values:

[ 1] #include <iostream.h>
[ 2] #include <string.h>
[ 3]
[ 4] enum Colors { RED, BLUE, GREEN };
[ 5]
[ 6] istream &operator >> ( istream &s, Colors &c )
[ 7] {
[ 8]	char string[20];
[ 9]	s >> string;
[10]	if ( strcmp( string, "RED" ) == 0) c = RED;
[11]	else if ( strcmp( string, "GREEN" ) == 0 ) c = GREEN;
[12]	else if ( strcmp( string, "BLUE" ) == 0 ) c = BLUE;
[13]	else cerr << "Error reading input";
[14]
[25]	return s;
[16] }
[17]
[18] main()
[19] {
[20]	Colors color;
[21]	cin >> color;
[22]	cout << "color = " << color << "\n";
[23] }

This worked fine under CC 1.2, and it continues to work fine with g++, but
it fails to work with the CC 2.0 iostream library.  The problem is that the
"wrong" operator>> is being called in line 21.  

cin is not of type istream, but of type istream_withassign, so line 21
wants to match a function with a signature of

    operator>>(istream_withassign, Colors).

There is no exact match for such a signature, so cfront considers other
possibilities.  The two relevant ones are

    operator>>(istream, Colors)           // the "right" one
    operator>>(istream_withassign, int)   // the "wrong" one (from istream.h)

From p. 88 of the C++ Reference Manual, we find the following rules for
argument matching (to be applied in this order):

    [1] Exact match: sequences of zero or more trivial convertions are
        better than all other sequences.
    [2] Match with promotions: of sequences not mentioned in [1],
        those that contain integral promotions [and some others] are
    	better than all others.
    [3] Match with standard convertions: of sequences not mentioned in
    	[2], those with only standard and trivial convertions are
	    better than all others.  [Derived to base convertions are
	    standard convertions.]
    [4] Match with convertions requiring temporaries.
    [5] Match with user-defined convertions.

The "wrong" function is chosen because it can be matched by applying rule
[2]:  promotion of an enum to an int.  To choose the "right" function would
call for an application of rule [3]:  standard conversion of a derived
class (istream_withassign) to a base class (istream).

The program can be fixed by redeclaring operator>> as follows:

    istream &operator >> ( istream_withassign &s, Colors &c )

This strikes me as needlessly complex: why should I have to know about the
details of the istream library in order to do something simple like read in
enumerated type values?  It is also asymmetrical with outputting enum
values, since ostreams work perfectly fine there.  Is there a better way to
approach this problem?

(This issue arose in the context of teaching students C++.  We taught them
enums early on, before they knew about classes, and in 1.2 everything fit
together nicely.  With the change in the iostream library, input of enums
is no longer trivial.  We'd like to make it trivial again :-).

For the record, Moises Lejter was responsible for figuring out why the
above program didn't work.)

Scott