C++ Logo

std-discussion

Advanced search

Re: Throwing noncopyable temporaries

From: Lénárd Szolnoki <cpp_at_[hidden]>
Date: Fri, 02 Sep 2022 11:11:54 +0100
On 2 September 2022 09:32:19 BST, "Lénárd Szolnoki via Std-Discussion" <std-discussion_at_[hidden]> wrote:
>Hi,
>
>On 2 September 2022 07:19:35 BST, Jens Maurer via Std-Discussion <std-discussion_at_[hidden]> wrote:
>>On 01/09/2022 13.55, Schneider, Robert via Std-Discussion wrote:
>>> Hi,
>>>
>>> GCC and clang at the moment allow throwing of noncopyable prvalue expressions:
>>>
>>> struct foo
>>> {
>>> foo() = default;
>>> foo(foo const&) = delete;
>>> foo(foo&&) = delete;
>>> };
>>>
>>> void bar()
>>> {
>>> throw foo();
>>> }
>>> https://compiler-explorer.com/z/d7h8f8ror
>>>
>>> I was wondering if that's intended, since I can't quite relate it to the wording of 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]).
>>>
>>> As far as I understand, mandatory copy elision means that the constructor selected for copy-initialization in this case is the _default constructor_. Copy-initialization doesn't even consider copy/move constructors here. The part about "the thrown object as an lvalue" doesn't make sense to me in this context.
>>
>>When we throw an exception, we want to make sure it can be caught:
>>
>>void bar()
>>{
>> try {
>> throw foo();
>> } catch (foo x) { // invokes copy constructor using an lvalue "foo"
>>
>> }
>>}
>>
>>This situation could also be handled by making the handler ill-formed.
>
>From the wording it's not clear to me if it's the throw expression and/or the handler should be ill-formed. As throw is an expression, it can be used in SFINAE context too. IMO this should work, but it doesn't:
>
>template <typename T>
>concept throwable = requires(T (*fptr)()) {
> throw fptr();
>};
>
>Probably throw and catch should be ill-formed at the same time.

There is probably some more nuance here, as there could be slicing between the thrown object and the handler. The base and derived objects can be independently non-copiable and non-movable.

Some interesting manifestation of the gcc bug:

https://godbolt.org/z/4jGco59jr

>>The interesting aspect is that gcc differs in its behavior depending on
>>whether S has data members or not:
>>
>>
>>struct S
>>{
>> S() = default;
>> S(const S&) = delete;
>> // int x = 0; // #1
>>};
>>
>>int main()
>>{
>> try {
>> throw S();
>> } catch (S s) {
>> return 1;
>> }
>>}
>>
>>
>>but if "int x" is present, I get:
>>
>>
>>x.cc:13:14: error: use of deleted function ‘S::S(const S&)’
>> 13 | } catch (S s) {
>> | ^
>>x.cc:5:3: note: declared here
>> 5 | S(const S&) = delete;
>> | ^
>>
>>
>>That feels seriously inconsistent.
>>
>>
>>> Anyway, was this change intended? Should the example above compile?
>>
>>The example you gave should not compile, per the current wording.
>>
>>Jens
>>--
>>Std-Discussion mailing list
>>Std-Discussion_at_[hidden]
>>https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>--
>Std-Discussion mailing list
>Std-Discussion_at_[hidden]
>https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion

Received on 2022-09-02 10:11:58