Bug 93106 - [c++2a] Deleted move constructor is not selected when returning an automatic variable
Summary: [c++2a] Deleted move constructor is not selected when returning an automatic ...
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 9.2.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: accepts-invalid
: 93929 (view as bug list)
Depends on:
Blocks:
 
Reported: 2019-12-30 14:48 UTC by Yunrui Wang
Modified: 2023-04-27 20:31 UTC (History)
5 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2021-05-13 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Yunrui Wang 2019-12-30 14:48:28 UTC
According to the c++ standard (draft n4835) the following code should not compile because it references a deleted function:

struct not_movable {
    not_movable() = default;
    not_movable(not_movable const&) = default;
    not_movable(not_movable&&) = delete;
};

not_movable foo() {
    not_movable obj;
    return obj;
}

When returning obj, the overload resolution should successfully select the deleted move constructor, therefore resulting in a compiler error.

The code has been tested under gcc 9.2 with compiler flags -std=c++2a.

Quotes from the standard: http://eel.is/c++draft/class.copy.elision#3
Comment 1 Jakub Jelinek 2020-01-02 16:08:31 UTC
What you cite doesn't say anything that would make the testcase invalid.
The standard says that for an implicitly movable entity seen in return statement shall first perform overload resolution for the copy constructor as if it was an rvalue, which in this case fails because the move ctor is deleted.  And in that case, another overload resolution is performed, this time considering the implicitly movable entity as lvalue, and that succeeds.  So, if copy-ellision wouldn't be performed, not_movable::not_movable(not_movable const&) would be invoked, but in this case copy-ellision is invoked and thus it is default constructed into the return value object in the caller.
Comment 2 Yunrui Wang 2020-01-03 15:04:08 UTC
(In reply to Jakub Jelinek from comment #1)
> What you cite doesn't say anything that would make the testcase invalid.
> The standard says that for an implicitly movable entity seen in return
> statement shall first perform overload resolution for the copy constructor
> as if it was an rvalue, which in this case fails because the move ctor is
> deleted.  And in that case, another overload resolution is performed, this
> time considering the implicitly movable entity as lvalue, and that succeeds.
> So, if copy-ellision wouldn't be performed,
> not_movable::not_movable(not_movable const&) would be invoked, but in this
> case copy-ellision is invoked and thus it is default constructed into the
> return value object in the caller.

The first overload resolution should succeed by selecting the deleted move constructors. Deleted functions are defined and are considered for overload resolution (unless otherwise specified - there are some edge cases, but not in this example).
Comment 3 Jason Merrill 2020-01-06 16:39:26 UTC
Confirmed.  check_return_expr can't use convert_for_initialization to test whether to treat the returned lvalue as an rvalue.
Comment 4 Marek Polacek 2020-01-30 17:05:17 UTC
(In reply to Jason Merrill from comment #3)
> Confirmed.  check_return_expr can't use convert_for_initialization to test
> whether to treat the returned lvalue as an rvalue.

Indeed: convert_for_initialization -> perform_implicit_conversion_flags -> convert_like_real will build_temp for the rvalue version of OBJ, so it calls build_special_member_call -> build_new_method_call_1.

Here we have three candidates:

  X::X(X&&)
  constexpr X::X(const X&)
  constexpr X::X()

the last one is not viable so splice_viable kills it.  And tourney selects

  X::X(X&&)

as expected.  But we pass it to build_over_call and that will not complain and just return error_mark_node for a DECL_DELETED_FN function.  Then the second stage of the two-stage overload resolution succeeds.  I guess we need to stop and issue an error when we found a move ctor, but it's deleted (but not if we don't find a move ctor at all).

(Came here in the context of PR91212 where this convert_for_initialization selects the wrong overload.)
Comment 5 Jonathan Wakely 2020-02-25 15:13:17 UTC
*** Bug 93929 has been marked as a duplicate of this bug. ***
Comment 6 Jonathan Wakely 2023-01-30 09:27:50 UTC
*** Bug 108594 has been marked as a duplicate of this bug. ***
Comment 7 Arthur O'Dwyer 2023-04-27 17:44:23 UTC
According to godbolt.org, Bug 93929, Bug 108594, and this Bug 93106 all appear to be fixed now (failed in GCC 12, correct behavior in GCC 13.1).
Comment 8 Yunrui Wang 2023-04-27 20:31:38 UTC
Issue has been resolved in gcc 12.1.