Home > other >  Why is std::move generating instructions here?
Why is std::move generating instructions here?

Time:04-14

I heard time and again that std::move(t) is more or less only a fancy way of saying static_cast<T&&>(t) and would not generate any instructions. When I was playing around now with std::move on godbolt in order to better understand move semantics I saw that it does (or at least may) generate instructions. In this example

#include <iostream>
using namespace std;

struct S {
    S() { cout << "default ctor" << endl; }
    S(S&& s) {
        i = s.i;
        s.i = 0;
        cout << "move ctor" << endl;
    }
    int i;
};

void foo(S s) { cout << "Foo called with " << s.i << endl; }

int main() {
    S s;
    foo(static_cast<S&&>(s));
    foo(std::move(s));
}

the calls to foo lead to the following assembly output

        ; ... snip ...
        lea     rdi, [rbp - 16]
        lea     rsi, [rbp - 8]
        call    S::S(S&&) [base object constructor]
        lea     rdi, [rbp - 16]
        call    foo(S)
        lea     rdi, [rbp - 8]
        call    std::remove_reference<S&>::type&& std::move<S&>(S&)
        lea     rdi, [rbp - 24]
        mov     rsi, rax
        call    S::S(S&&) [base object constructor]
        lea     rdi, [rbp - 24]
        call    foo(S)
        ; ... snip ...
std::remove_reference<S&>::type&& std::move<S&>(S&): # @std::remove_reference<S&>::type&& std::move<S&>(S&)
        push    rbp
        mov     rbp, rsp
        mov     qword ptr [rbp - 8], rdi
        mov     rax, qword ptr [rbp - 8]
        pop     rbp
        ret

Can someone please explain this to me? I can't much sense of what this std::remove_reference<S&>::type&& std::move<S&>(S&) function is supposed to do and why there is this apparent contraction to what's commonly told.

CodePudding user response:

As for std::remove_reference<S&>::type&& std::move<S&>(S&) Josuttis explains this in his C move semantics.

Basically what it does is similar to static_cast<T&&> but with the means of type traits. It allows any value category to be passed in (thus, either lvalue or rvalue reference) then it cuts off the reference part and applies to rvalue-ref one. As for the extra instructions: any optimizer should inline those calls anyway.

With optimization turned off and foo defined as void foo(const S& s); to reduce noise:

foo(static_cast<S&&>(s));

        leaq    -4(%rbp), %rax
        movq    %rax, %rdi
        call    foo(S const&)

foo(std::move(s));

        leaq    -4(%rbp), %rax
        movq    %rax, %rdi
        call    std::remove_reference<S&>::type&& std::move<S&>(S&)
        movq    %rax, %rdi

With -O1, both result in the same:

        leaq    12(%rsp), %rdi
        call    foo(S const&)

CodePudding user response:

You are compiling without optimisations. So you see exactly what is written without any attempt to simplify or inline functions.

Generated code is roughly eqivalent to what type&& foo(type& x) { return x; } would generate, which is what move does.

Studying assembly generated without optimisations turned on is exercise in futility.

  • Related