Topic: Constructor Questions


Author: sdm@cs.brown.edu (Scott Meyers)
Date: 12 Dec 90 21:28:57 GMT
Raw View
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.