6

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?

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
Fedor
  • 17,146
  • 13
  • 40
  • 131

1 Answers1

5

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)

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • 2
    Why am I only the second upvoter? – Bathsheba Nov 02 '21 at 09:44
  • 1
    I'm pretty sure mandatory copy elision does not apply here. If we look at exception throwing, the object that is being copied initialized from is an lvalue: https://timsong-cpp.github.io/cppwp/except#throw-3 – NathanOliver Nov 02 '21 at 12:13
  • 1
    Also see paragraph 5 which calls out that the constructors must be accessible if it is optimized: https://timsong-cpp.github.io/cppwp/except#throw-5 – NathanOliver Nov 02 '21 at 12:17
  • @Bathsheba because the end of the answer (about copy elision) is hard to read and not convincing –  Nov 20 '21 at 17:00