This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 113d. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2024-03-20


670. Copy initialization via derived-to-base conversion in the second step

Section: 9.4  [dcl.init]     Status: CD4     Submitter: Jason Merrill     Date: 20 December 2007

[Accepted at the June, 2016 meeting as part of paper P0135R1.]

In this example:

    struct A {};

    struct B: A {
       B(int);
       B(B&);
       B(A);
    };

    void foo(B);

    void bar() {
       foo(0);
    }

we are copy-initializing a B from 0. So by 12.2.2.5 [over.match.copy] we consider all the converting constructors of B, and choose B(int) to create a B. Then, by 9.4 [dcl.init] paragraph 15, we direct-initialize the parameter from that temporary B. By 12.2.2.4 [over.match.ctor] we consider all constructors. The copy constructor cannot be called with a temporary, but B(A) is callable.

As far as I can tell, the Standard says that this example is well-formed, and calls B(A). EDG and G++ have rejected this example with a message about the copy constructor not being callable, but I have been unsuccessful in finding anything in the Standard that says that we only consider the copy constructor in the second step of copy-initialization. I wouldn't mind such a rule, but it doesn't seem to be there. And implementing issue 391 causes G++ to start accepting the example.

This question came up before in a GCC bug report; in the discussion of that bug Nathan Sidwell said that some EDG folks explained to him why the testcase is ill-formed, but unfortunately didn't provide that explanation in the bug report.

I think the resolution of issue 391 makes this example well-formed; it was previously ill-formed because in order to bind the temporary B(0) to the argument of A(const A&) we needed to make another temporary B, and that's what made the example ill-formed. If we want this example to stay ill-formed, we need to change something else.

Steve Adamczyk:

I tracked down my response to Nathan at the time, and it related to my paper N1232 (on the auto_ptr problem). The change that came out of that paper is in 12.2.4.2 [over.best.ics] paragraph 4:

However, when considering the argument of a user-defined conversion function that is a candidate by 12.2.2.4 [over.match.ctor] when invoked for the copying of the temporary in the second step of a class copy-initialization, or by 12.2.2.5 [over.match.copy], 12.2.2.6 [over.match.conv], or 12.2.2.7 [over.match.ref] in all cases, only standard conversion sequences and ellipsis conversion sequences are allowed.

This is intended to prevent use of more than one implicit user- defined conversion in an initialization.

I told Nathan B(A) can't be called because its argument would require yet another user-defined conversion, but I was wrong. I saw the conversion from B to A and immediately thought “user-defined,” but in fact because B is a derived class of A the conversion according to 12.2.4.2 [over.best.ics] paragraph 6 is a derived-to-base Conversion (even though it will be implemented by calling a copy constructor).

So I agree with you: with the analysis above and the change for issue 391 this example is well-formed. We should discuss whether we want to make a change to keep it ill-formed.