35
#include<iostream>
using namespace std;

struct B{};

struct A
{
    A(const B &)
    {
        cout<<"A(const B &)"<<endl;
    }
    A(B &&)
    {
        cout<<"A(B &&)"<<endl;
    }
};

A get()
{
    B b;
    return b;
}

int main()
{
    get();
}

I tested the code with VC++14.2 and GCC 5.4.0, both of them output:

A(B &&)

Why is the output not

A(const B &)

?

Does this code have any relation with copy elision? (But A and B are different types, so copy elision should not work here)

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
Caesar
  • 971
  • 6
  • 13
  • 1
    This is actually a very interesting question. I find the wording in 12.8/32 somewhat unclear: It's not clear whether the type of the return expression has to be the same as the return type of the function in order for the rule to apply. I think you could read it as if *any* returned object with automatic storage is considered as designated by an rvalue. Different compilers [think differently about this](http://melpon.org/wandbox/permlink/meIAgmvSOVq435vy). – Kerrek SB Aug 13 '16 at 13:10
  • [CWG1579](http://wg21.link/cwg1579) seems relevant here. – Kerrek SB Aug 13 '16 at 13:13
  • @KerrekSB according to (31.1), `when the expression is the name of a non-volatile automatic object (other than a function parameter or a variable introduced by the exception-declaration of a handler (15.3)) with the same type (ignoring cv-qualification) as the function return type`, I think the `same type` have to be exactly as same as `return type`. That means `copy elision` will work if I return a `A` object. But the implicit conversion from `B` to `A` happens before the `copy elision`, `copy elision` should not affect the `implicit conversion`. – Caesar Aug 13 '16 at 13:19
  • 1
    This isn't the issue, but don't use `std::endl` unless you need the extra stuff that it does. `'\n'` ends a line. – Pete Becker Aug 13 '16 at 14:09

2 Answers2

29

The return-as-rvalue rules changed in response to the review before publication of C++14. The change was added late in the process and is captured by CWG Issue 1579, that amends 12.8/32 with the wording:

or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body

This means that returning any local variable now considers the object designated by that variable as if it were an rvalue first (trying again if overload resolution fails).

Since the CWG issue was accepted as a defect in the language, compilers may implement this new rule even in "C++11 mode". The point of a defect is that "it was always meant to work that way", so this isn't strictly speaking a change between C++11 and C++14, but rather, the meaning of C++11 was modified in 2014.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • The statement can be found in n4594 §12.8 32. I thought that only work when `copy elision` is met!!! – Caesar Aug 13 '16 at 13:37
  • @Caesar: Yes, that how it used to be. But the linked Core issue was that "it would be nice if it also worked more generally", and then there was a national body comment to have that changed, so it got changed. The linked issue contains a motivating example very similar to yours. – Kerrek SB Aug 13 '16 at 13:38
  • @Caesar Yeah, I make this mistake when reading that every time. It's a very complex sentence. But think about it this way. If you're returning an automatic storage variable by name, it's certainly safe to move from, so why not have the language automatically do that for you? – Barry Aug 13 '16 at 13:54
  • @Barry actually, I don't like "too smart" compilers. haha – Caesar Aug 13 '16 at 14:01
  • Note that unlike in the case of copy elision, adding `std::move` explicitly isn't a pessimisation in this new case. Rather, if you add `std::move`, you're explicitly request an rvalue constructor (even if that ends up defined as deleted), whereas if you don't, then you get whichever constructor fits best. So while this change was a breaking change, it didn't have the effect that formerly good code is now bad code; existing good code is still appropriate. It's just that a lot of formerly bad code has now silently become better. – Kerrek SB Aug 13 '16 at 14:33
  • Yes. The compiler will call const l-value reference constructor only when you don't provide (non-deleted or deleted) r-value reference constructor. – Caesar Aug 13 '16 at 14:38
  • 1
    @Caesar: Sorry, I was wrong, and what you said was right - if you explicitly delete, then the program is indeed illformed. So it's in fact Clang that's wrong, and that's a known bug. It's only if an *implicitly defined* move constructor would be selected that's deleted that overload resolution fails and is tried again as an lvalue. – Kerrek SB Aug 17 '16 at 08:21
5

Copy elision is related to A, not being constructed in the get() stack-frame. What happens is that the returned b (that's temporary since "going to die because of return": this is since C++14), is use to construct the copy-elided A in main stack-frame.

And since temporary objects binds r-value references that's what you observed.

Emilio Garavaglia
  • 20,229
  • 2
  • 46
  • 63