In summary, clang and MSVC are both correct. GCC calls the wrong constructor.
There are two separate objects
To understand the required behavior, we must understand that in the following code, there are two objects:
int main() {
try {
throw A{};
}
catch ( const A ) {
}
}
Firstly, [except.throw] p3 states
Throwing an exception initializes a temporary object, called the exception object. [...]
Secondly [except.handle] p14.2 explains,
The variable declared by the exception-declaration, of type cv T
or cv T&
, is initialized from the exception object, of type E
, as follows:
- [...]
- otherwise, the variable is copy-initialized from an lvalue of type
E
designating the exception object.
GCC calls the wrong constructor
What happens is similar to:
A temporary = throw A{};
const A a = temporary;
The fact that the variable in the handler is const
doesn't affect the cv-qualifications of the temporary object because they are two separate objects.
The temporary object is not const
, so A(A&)
is a better match during initialization. GCC is wrong.
MSVC is allowed to perform copy elision
Furthermore, it might possible that copy elision is performed.
The standard even has an example of that in [except.throw] p7:
int main() {
try {
throw C(); // calls std::terminate if construction of the handler's
// exception-declaration object is not elided
} catch(C) { }
}
[class.copy.elision] p1.4 confirms that it's allowed, even if cv-qualifications don't match:
[...] copy elision, is permitted in the following circumstances ([...]):
- [...]
- when the exception-declaration of an exception handler declares an object of the same type (except for cv-qualification) as the exception object, the copy operation can be omitted by [...]
A
and const A
are not the same type, but they only differ in cv-qualification, so copy elision is allowed.
const
though): en.wikipedia.org/wiki/Copy_elision – Derive