Reputation: 1062
Since C++17, the meaning of prvalue has changed, which makes copy elision guaranteed in some cases. From cppreference, the copy/move constructors need not be present or accessible in that case.
When an exception is thrown, the exception object is copy-initialized, and the copy/move may be subject to copy elision. But is it required that the copy/move constructor must be available at this time?
From [except.throw]:
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 destructor is potentially invoked ([class.dtor]).
The standard mentions that the corresponding constructor must be non-deleted and accessible, even if it is elided.
However, I tested and found that both GCC and Clang allow to throw when the corresponding constructor is deleted:
struct A {
A() = default;
A(A&&) = delete;
};
try {
throw A{}; // copy elision
} catch(...) {}
The code compiles, but it does not seem to meet the requirements of the standard. If I lower the version from C++17 to C++14, they will both report errors:
struct A {
A() = default;
A(A&&) = delete;
};
try {
throw A{}; // error: call to deleted constructor of 'A'
} catch(...) {}
Is my understanding of the standard wrong, or is it just the compiler relaxing the restrictions?
Upvotes: 3
Views: 874
Reputation: 677
Firstly, C++17 guarantees copy elision neither when creating an exception object nor when activating an exception handler.
The requirement for the copy constructors to exist actually is simple - on the one hand, there are some cases when copy elision is just not sensible, including the both throwing and catching exceptions; on the other hand compilers are only permitted to perform the elision but not obligated. So the compiler would use the constructors in the cases.
The fact that your code does not implement the cases does not mean that your types (classes) are permitted to not support the cases (even though a compiler can successfully compile it). The cases are still valid for the language and must be implementable without modifying code your types (classes) used for exception.
Upvotes: 0
Reputation: 473437
Your understanding of the standard is correct, in so far as your analysis of that paragraph actually applies.
But it doesn't apply, because no constructor is ever considered for copy-initialization.
Guaranteed elision is something of a misnomer; it's a useful explanation of the concept, but it doesn't reflect exactly how it works as far as the standard is concerned. Guaranteed elision works by rewriting the meaning of prvalues so there never is a copy/move to be elided in the first place.
A a = A{};
is copy-initialization, but it doesn't even hypothetically call a copy/move constructor. The variable a
is initialized by the prvalue's initializer:
The result of a prvalue is the value that the expression stores into its context. A prvalue whose result is the value V is sometimes said to have or name the value V. The result object of a prvalue is the object initialized by the prvalue
a
is the "result object" initialized by the prvalue.
The same goes here. A{}
is a prvalue. The exception object is the "result object" to be initialized by the prvalue. There's no temporary object, no copy/move constructors that ever get considered for usage.
Upvotes: 2