sdm@cs.brown.edu (Scott Meyers) (12/13/90)
I thought I understood constructors, initialization, assignment, and type conversions, but now I'm not so sure. Either I'm confused or the ARM is poorly worded (or both). The ARM, p. 284, says: Objects of classes with constructors can be initialized with a parenthesized expression list. This list is taken as the argument list for a call of a constructor doing the initialization. Alternatively a single value is specified as the initializer using the = operator. This value is used as the argument to a copy constructor. Typically that call of a copy constructor can be eliminated. Taken literally, this means: Foo x(10); // calls Foo::Foo(int) with argument 10 Foo x = 10; // calls Foo::Foo(const Foo&) with argument 10 But a copy constructor, by definition (ARM, p. 264), must take a Foo& as its first argument, so I think what p. 284 is supposed to mean is: Foo x(10); // calls Foo::Foo(int) with argument 10 Foo x = 10; // calls Foo::Foo(int) with argument 10 and then // calls Foo::Foo(Foo&) with the resulting object The comment on p. 284 that the call to the copy constructor can typically be eliminated implies that a compiler is free to assume that Foo::Foo(10) and Foo::Foo(Foo::Foo(10)) are semantically equivalent. This is the only place I know of where the ARM implies that certain sequences of function calls should be semantically interchangable. Is that really what it is saying? The interpretation of Foo x = 10 above is consistent with what we find on p. 270: Type conversions of class objects can be specified by constructors and by conversion functions. ... a function expecting an argument of type X can be called not only with an argument of type X but also with an argument of type T where a conversion from T to X exists. But that seems contradicted on p. 271: When no constructor for class X accepts the given type, no attempt is made to find other constructors or conversion functions to convert the assigned value into a type acceptable to a constructor for class X. For example: class X { public: X(int) {} }; class Y { public: Y(X) {} }; Y a = 1; // illegal: Y(X(1)) not tried This makes it sound like the argument conversion rules for constructors are supposed to be more restrictive than the argument conversion rules for non-constructor functions. In addition, the passage on p. 271 is alarming in its use of the phrase "assigned value," since that implies a call to operator=, but in fact no assignment is taking place, only an initialization, right? Continuing with example in the ARM using classes X and Y, consider this declaration: Y a(1); There is no constructor in Y taking an int, and per the ARM p. 271 we assume that Y::Y(X::X(int)) is not called, so this should be an error, right? Unfortunately, cfronts 2.0 and 2.1 accept it (calling both constructors), as does g++ 1.37.2. Is this a bug in the compilers, or is there some reason why the following should hold: Y a = 1; // illegal as in ARM Y a(1); // legal for some reason I don't see To summarize this posting: 1. Are compilers free to assume that copy constructor calls can be eliminated when they follow constructor calls? 2. Are the rules for converting types of actual parameters to types of formal parameters really more restrictive for constructors than for all other functions? 3. Is the reference to an "assigned value" on p. 271 incorrect, because no assignment is taking place, only an initialization? 4. In the example on p. 271, is there some reason why "Y a = 1" is illegal and "Y a(1)" is legal? Scott ------------------------------------------------------------------------------- What's the difference between Willie Horton and Buddy Cianci? Willie Horton was convicted of his felony in Massachusetts.