Search code examples
c++visual-c++rvalue-reference

Different constructor calls depending on compiler


Running the code supplied in this question:

Why move return an rvalue reference parameter need to wrap it with std::move()?

#include <string>
#include <iostream>
#include <utility>

template<typename T>
class TD;

class Widget {
public:
    explicit Widget(const std::string& name) : name(name) {
        std::cout << "Widget created with name: " << name << ".\n";
    }

    Widget(const Widget& w) : name(w.name) {
        std::cout << "Widget " << name << " just got copied.\n";
    }

    Widget(Widget&& w) : name(std::move(w.name)) {
        std::cout << "Widget " << name << " just got moved.\n";
    }

private:
    std::string name;
};

Widget passThroughMove(Widget&& w) {
    // TD<decltype(w)> wType;
    // TD<decltype(std::move(w))> mwType;
    return std::move(w);
}

Widget passThrough(Widget&& w) {
    return w;
}

int main() {
    Widget w1("w1");
    Widget w2("w2");

    Widget wt1 = passThroughMove(std::move(w1));
    Widget wt2 = passThrough(std::move(w2));

    return 0;
}

Yields different results depending on the compiler I use. When compiling using the latest Visual Studio (tried both C++14 and C++17) I get the following results:

Widget created with name: w1.
Widget created with name: w2.
Widget w1 just got moved.
Widget w2 just got moved. //<---

Ran and compiled this code online and the results were different. In the provided question the user also receives the same results:

Widget created with name: w1.
Widget created with name: w2.
Widget w1 just got moved.
Widget w2 just got copied. //<---

Why is w2 being moved when using Visual Studio and copied when using various different compilers?


Solution

  • MSVC has implemented P1825R0 - More implicit moves

    As can be seen in the Microsoft C++ language conformance table section of the Visual Studio C++ documentation, this is Visual Studio implementing

    as of Visual Studio 2019 version 16.4, compiler MSVC version 19.24.

    The essential parts of P1825R0 has been added to [class.copy.elision]/3 [emphasis mine]:

    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 might be used instead of a copy operation:

    • (3.1) 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
    • (3.2) ...

    overload resolution to select the constructor for the copy or the return_­value overload to call is first performed as if the expression or operand were an rvalue. [...].

    Using godbolt.ms, and the following contrived example

    #include <memory>
    
    struct Foo {
        Foo(int num) : num(num) {}
        Foo(const Foo& f) : num(f.num) {}
        Foo(Foo&& f) : num(std::move(f.num)) {}
        Foo& operator=(const Foo&) = delete;
        Foo& operator=(Foo&&) = delete;
        int num;
    };
    
    Foo maybeConsumeMyPreciousFoo(Foo&& foo, bool consume_foo) {
        if (consume_foo) { return std::move(foo); }
        else { return foo; }  // "Should not move" (as we'll see this is an invalid assumption)
    }
    

    we can inspect, in particular, the generated assemby of the line else { return foo; } in the maybeConsumeMyPreciousFoo(...) function;

    for MSVC v19.23:

    mov     rdx, QWORD PTR foo$[rsp]
    mov     rcx, QWORD PTR __$ReturnUdt$[rsp]
    call    Foo::Foo(Foo const &)             ; Foo::Foo
    mov     rax, QWORD PTR __$ReturnUdt$[rsp]
    

    and for MSVC v19.24:

    mov     rdx, QWORD PTR foo$[rsp]
    mov     rcx, QWORD PTR __$ReturnUdt$[rsp]
    call    Foo::Foo(Foo &&)       ; Foo::Foo
    mov     rax, QWORD PTR __$ReturnUdt$[rsp]
    

    respectively, showing that the branch, for the latter version, actually moves from the foo parameter as if it was an rvalue.


    GCC and Clang are yet to implement P1825R0

    GCC:

    C++ Standards Support in GCC:

    [...]

    C++2a Language Features

    [...]

    DR: More implicit moves (merge P0527R1 and P1155R3)

    Available in GCC?: No

    Clang:

    C++ Support in Clang

    [...]

    ###C++20 implementation status

    P1825R0 not even listed.

    Finally, cppreference's C++ compiler support page also lists P1825R0 as non-supported for both Clang and GCC.