Bug 65869 - Incorrect overload resolution in function return statement
Summary: Incorrect overload resolution in function return statement
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 4.9.2
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: accepts-invalid
Depends on:
Blocks:
 
Reported: 2015-04-23 21:55 UTC by Botond Ballo
Modified: 2021-08-06 08:45 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2015-04-24 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Botond Ballo 2015-04-23 21:55:52 UTC
GCC accepts the following code:


struct Base {};

struct Derived : Base {
    Derived();
    explicit Derived(const Derived&);
    explicit Derived(Derived&&);
    explicit Derived(const Base&);
    Derived(Base&&);
};

Derived foo() {
  Derived result;
  return result;
}

int main() {
  Derived result = foo();
}


I believe this code is invalid. Here's why:

  - [class.copy] p32 specifies that overload resolution
    to select the constructor for the copy in the return
    statement is to be performed in two stages.

  - First, it's performed treating the object to be copied
    ('result' here) as an rvalue.

  - Here, the 'Derived(Base&&)' constructor is selected.
    (The explicit constructors are not candidates in this
    context.)

  - However, p32 says that "if the type of the first
    parameter of the selected constructor is not an rvalue
    reference to the object's type", then overload 
    resolution is performed again, treating the object as 
    an rvalue.

      - Here, the type of the first parameter of the
        selected constructor is 'Base&&'. This is an rvalue
        reference, but it's not "to the object's type"
        ("the object's type" being 'Derived'). Therefore,
        this provision is activated.

  - The second overload resolution fails, because the only
    candidate (again, the explicit constructors are not
    candidates) has an rvalue reference parameter, which
    cannot bind to the lvalue that we're not treating the
    object as being.

A more detailed and better-formatted explanation can be found here [1].

Clang rejects this code, showing the error from the failure
of the second overload resolution:

test.cpp:13:10: error: no matching constructor for initialization of 'Derived'
  return result;
         ^~~~~~
test.cpp:8:5: note: candidate constructor not viable: no known conversion from 'Derived' to 'Base &&' for 1st argument
    Derived(Base&&);
    ^
test.cpp:4:5: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
    Derived();
    ^

[1] http://stackoverflow.com/a/29834426/141719
Comment 2 Botond Ballo 2015-04-24 06:57:22 UTC
(In reply to Marc Glisse from comment #1)
> http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1579

I don't think the resolution of this issue affects the validity of my code example, i.e. it remains invalid.

The resolution of this issue makes the following code valid:

  Derived foo() {
    Base result;
    return result;
  }

(because it allows [class.copy] p32 to apply at all, where previously it didn't apply because the type of the object to be copied, Base, was different from the return type of the function Derived).

In my example, however:

  Derived foo() {
    Derived result;
    return result;
  }

p32 already applies, with or without the added wording. The problem is wording further down in the paragraph which prevents the 'Derived(Base&&)' constructor from being selected in the first overload resolution when the type of the object is Derived.
Comment 3 Jonathan Wakely 2015-04-24 10:07:52 UTC
(In reply to Botond Ballo from comment #0)
>   - However, p32 says that "if the type of the first
>     parameter of the selected constructor is not an rvalue
>     reference to the object's type", then overload 
>     resolution is performed again, treating the object as 
>     an rvalue.

Typo, this should say lvalue.
Comment 4 Botond Ballo 2015-04-25 02:57:12 UTC
>   - The second overload resolution fails, because the only
>     candidate (again, the explicit constructors are not
>     candidates) has an rvalue reference parameter, which
>     cannot bind to the lvalue that we're not treating the
>     object as being.

This is also a typo, should say "cannot bind to the lvalue that we're treating the object as being".
Comment 5 Andrew Pinski 2021-08-04 21:15:42 UTC
Some interesting history here:

GCC7.5.0 and before accepted the code in C++11, C++14 and C++17 modes
GCC8+ reject the code in C++11, C++14, and C++17 modes with the following error message:
<source>: In function 'Derived foo()':
<source>:14:10: error: cannot bind rvalue reference of type 'Base&&' to lvalue of type 'Base'
   return result;
          ^~~~~~
<source>:9:5: note:   initializing argument 1 of 'Derived::Derived(Base&&)'
     Derived(Base&&);
     ^~~~~~~

GCC11+ started to accept the code in C++20 mode.



GCC8 change was due to PR 80452.

GCC11 in C++20 mode seems most likely due to PR 91427.  The patch for PR 91427, made it sound like it was defect report for earlier versions of the C++ standards too:
    For the time being I'm limiting the new semantics to C++20 mode; since it
    was moved as a DR, we will probably want to apply the change to other
    standard modes as well once we have a better sense of the impact on existing
    code, probably in GCC 12.