c++exceptioninitializationlanguage-lawyercopy-initialization

Can not-copyable class be caught by value in C++?


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?


Solution

  • Clang is correct. (Thanks for @NathanOliver's comments.)

    [except.throw]/3

    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]).

    [except.throw]/5

    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)