In the next program, struct B
with deleted copy-constructor is thrown and caught by value:
struct B {
B() = default;
B(const B&) = delete;
};
int main() {
try {
throw B{};
}
catch( B ) {
}
}
Clang rejects the code with an expected error:
error: call to deleted constructor of 'B'
catch( B ) {
However GCC accepts the program finely, demo: https://gcc.godbolt.org/z/ed45YKKo5
Which compiler is right here?
CodePudding user response:
Clang is correct. (Thanks for @NathanOliver's comments.)
Throwing an exception copy-initializes ([dcl.init], [class.copy.ctor]) a temporary object, called the exception object. An lvalue denoting the temporary is used to initialize the variable declared in the matching handler ([except.handle]).
When the thrown object is a class object, the constructor selected for the copy-initialization as well as the constructor selected for a copy-initialization considering the thrown object as an lvalue shall be non-deleted and accessible, even if the copy/move operation is elided ([class.copy.elision]).
The exception object is considered as an lvalue, in the copy-initialization of the parameter of the catch clause, the copy constructor is selected. The copy operation might be elided as an optimization; but the copy constructor still has to be present and accessible.
I've reported this as gcc bug 103048.
BTW the copy-initialization of the exception object caused by throw B{};
is fine because of mandatory copy elision (since C 17).
In the initialization of an object, when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the variable type:
T x = T(T(f())); // only one call to default constructor of T, to initialize x
First, if T is a class type and the initializer is a prvalue expression whose cv-unqualified type is the same class as T, the initializer expression itself, rather than a temporary materialized from it, is used to initialize the destination object: see copy elision (since C 17)