0

This is a follow-up question of my previous question.

Consider the following toy code:

#include <iostream>
using namespace std;
class X
{
public:
    X() { }

    X(X&& x)
    {
        cout << "move ctor\n";
    }

    /*X(X& x)
    {
        cout << "copy ctor\n";
    }*/

};

X f()
{
    static X x;
    X&& y = std::move(x);
    X& z = x;
    return y;
}

int main()
{
    f();
}

From my understanding on my previous question (i.e. class.copy.elision#3), I think it would cause an error (use of deleted function 'constexpr X::X(const X&)') in the above code to return y in f().

The thing is, I run the code in Visual Studio on my PC and it compiles and prints move ctor. So I test the code online using other compilers, and the results are that msvc and clang compile successfully while gcc gives the error that I'm expecting.

May I humbly ask if this is a bug of msvc and clang, and the program ought not to compile?

CPPL
  • 726
  • 1
  • 10

2 Answers2

5

The code is legal since C++20, according to cppreference.

There's a rule that returning a non-reference non-volatile local variable implicitly moves it. C++20 added the same rule for rvalue references to non-volatile types.

GCC accepts the code with -std=c++20. I'm unsure why Clang and MSVC accept it in earlier standard revisions, but I don't see this as a problem.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
3

Pre-C++20

The code is ill-formed for Pre-C++20 standard version as class.copy.elision states:

a move operation might be used instead of a copy operation

  • if the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function

(emphasis mine)

as you can see there is no mention of rvalue reference in the above quoted statement. And so there seems to be a bug in msvc and clang as they compile it for Pre-C++20.


C++20

But C++20, specifically allows the use of rvalue reference as quoted below:

An implicitly movable entity is a variable of automatic storage duration that is either a non-volatile object or an rvalue reference to a non-volatile object type. In the following copy-initialization contexts, a move operation is first considered before attempting a copy operation:

  • If the expression in a return ([stmt.return]) or co_­return ([stmt.return.coroutine]) statement is a (possibly parenthesized) id-expression that names an implicitly movable entity declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, or

(emphasis mine)

Thus the code is legal from C++20 and onwards and the move constructor can be used.

Jason
  • 36,170
  • 5
  • 26
  • 60
  • Thanks! May I further ask, for assignment, if it is true that move assignment is **not** called if and only if the expression on the RHS is an lvalue? (given move assignment operator defined) **UPDATE**: I think so, as the [Notes](https://en.cppreference.com/w/cpp/language/move_assignment) section stated *If both copy and move assignment operators are provided, overload resolution selects the move assignment if the argument is an rvalue ... and selects the copy assignment if the argument is an lvalue* – CPPL May 30 '22 at 01:36
  • @CPPL The quoted statement in my answer above only applies to **initialization** meaning for move constructor and not move assiginment. For assignments the normal(usual) thing will happen. If in an assignment, the rhs is an rvalue then move assignment can be used but if rhs is an lvalue then copy assignment which is what is said in the notes sections that you cited. – Jason May 30 '22 at 04:08