Return a named object of a class from a function (by value ) and implicit move rule?

The behavior of your program can be understood with the help of Automatic move from local variables and parameters:

If expression is a (possibly parenthesized) id-expression that names a variable whose type is either

  • a non-volatile object type or

  • a non-volatile rvalue reference to object type (since C++20)

and that variable is declared

  • in the body or

  • as a parameter of

    the innermost enclosing function or lambda expression,

then overload resolution to select the constructor to use for initialization of the returned value or, for co_return, to select the overload of promise.return_value() (since C++20) is performed twice:

  • first as if expression were an rvalue expression (thus it may select the move constructor), and
  • if the first overload resolution failed or
  • it succeeded, but did not select the move constructor (formally, the first parameter of the selected constructor was not an rvalue reference to the (possibly cv-qualified) type of expression) (until C++20)
  • then overload resolution is performed as usual, with expression considered as an lvalue (so it may select the copy constructor).

Now, lets apply this to your code snippet on case by case basis.

Example 1

In this case, as the move ctor is available and viable, the condition “first as if expression were an rvalue expression” is satisfied and hence the move ctor is selected we get the mentioned output.

class test {
public:
    test(int y) {
        printf(" test(int y)\n");
    }
    test() {
        printf(" test()\n");
    }
    test( const test& z) {
        printf(" test( const test&z)\n");
    }
    test(test&& s)noexcept{
            printf(" test(test&& s)\n");          
    }
    test& operator=(test e) {
        printf(" test& operator=( test e)\n");
        return *this;
    }
};
test Some_thing() {
    test i;
    return i;
}
int main()
{
    Some_thing();
    return 0;
}

Example 2

In this case, since you’ve provided the copy ctor test::test( test&), the compiler will not synthesize a move ctor for us. Note that not having a synthesized move ctor is different from having a deleted move ctor. Thus the condition “if the first overload resolution failed” is satisfied(because there is no move ctor) and the overload resolution is then performed for the second time which will now select the provided copy ctor and hence the mentioned output.

class test {
public:
    test(int y) {
        printf(" test(int y)\n");
    }
    test() {
        printf(" test()\n");
    }
    test( test& z) {
        printf(" test( test&z)\n");
    }
    test& operator=(test e) {
        printf(" test& operator=( test e)\n");
        return *this;
    }
};
test Some_thing() {
    test i;
    return i;
}
int main()
{
    Some_thing();
    return 0;
}

Example 3

In this case, you’ve explicitly deleted the move ctor. That is, your intent is that if someone tried to use the move ctor, then it should fail. So here, when the overload resolution happens for the first time, the move ctor is selected but since you’ve explicitly marked it as deleted fails immediately and hence the error.

class test {
public:
    test(test&& z) = delete;
    test(int y) {
        printf(" test(int y)\n");
    }
    test() {
        printf(" test()\n");
    }
    test( const test& z) {
        printf(" test( test&z)\n");
    }
    test& operator=(test e) {
        printf(" test& operator=( test e)\n");
        return *this;
    }
};
test Some_thing() {
    test i;
    return i;
}
int main()
{
  Some_thing();
    return 0;
}

Leave a Comment