I’ve been trying to understand the rules surrounding this bug, which interestingly enough used to cause g++ to crash and still causes clang as well as the sun compiler to crash.
Here is an interesting pair of test cases I came up with.
5.cpp gets the same result on at least 4 compilers. It compiles correctly, and prints B(1)
6.cpp does not compile on clang++ or g++.
This seems illogical to me. It does not call the copy constructor in 5.cpp but in 6.cpp it tails
to compile because it can’t find a valid copy constructor.
I have a number of other related test cases. I want to make a fix that really fixes this general class of issues and not just make the crash go away for that one test case.
On other compilers, both 5.cpp and 6.cpp get the same result.
Right, the standard requires that the copy constructor exist, but allows the copy itself to be elided. In C++11, a move constructor would work here as well.
C++11 [class.temporary]p1: Temporaries of class type are created in various contexts: …returning a prvalue (6.6.3)…
C++11 [class.copy]p31: When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object…in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv- unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value, [and] when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
Neither 5.cpp nor 6.cpp have the problematic pattern.
The issue is that (for the case in PR8539 comment#2), when trying to
construct a B from a B temporary, we can't use the B(B&) constructor
and try the B(const A&) constructor. That tries to copy-initialize an
A temporary from the B temporary, which looks at A's constructors and
selects the A(B) constructor.
Then overload resolution terminates and we perform the initialization,
and that tries to construct a B from a B temporary. And around we go.
It should be straightforward to detect this and reject this after
constructing an InitializationSequence; if the first step is a
SK_ConstructorInitialization which performs the same conversion as the
overall sequence, then we have an error.
I understand that 5.cpp and 6.cpp are not the source of the of the crash, but they involve part of what the crash is trying to do and the result was unexpected to me so I wanted to understand this better. At least two other compilers get a different result for 6.cpp than g++ and clang++.
I understand that 5.cpp and 6.cpp are not the source of the of the crash,
but they involve part of what the crash is trying to do and the result was
unexpected to me so I wanted to understand this better. At least two other
compilers get a different result for 6.cpp than g++ and clang++.
I would expect that MSVC accepts 6.cpp, because it allows non-const
references to bind to temporaries.
Perhaps the other compiler which accepts it does the same, or perhaps
it doesn't perform semantic checking for elidable copies, or perhaps
it doesn't suppress the implicit copy constructor when a B(B&)
constructor is declared...