12

Somewhat surprisingly (to me), the following two programs compile to different outputs, with the latter one having much better performance (tested with gcc and clang):

#include <vector>
int main()
{
    std::vector<int> a(2<<20);
    for(std::size_t i = 0; i != 1000; ++i) {
        std::vector<int> b(2<<20);
        a = b;
    }
}

vs.

#include <vector>
int main()
{
    std::vector<int> a(2<<20);
    for(std::size_t i = 0; i != 1000; ++i) {
        std::vector<int> b(2<<20);
        a = std::move(b);
    }
}

Could someone explain to me why the compiler does (or can) not automatically consider b an xvalue in the last assignment and apply move semantics without the explicit std::move cast?

Edit: Compiled with (g++|clang++) -std=c++11 -O3 -o test test.cpp

Marco A.
  • 43,032
  • 26
  • 132
  • 246
Xoph
  • 459
  • 2
  • 10
  • What are the parameters you're passing to the compilers? – Joe Sep 24 '14 at 09:25
  • 1
    My first guess is that this would change the semantics of the program in an unexpected way turning a copy into a move. – pmr Sep 24 '14 at 09:27
  • @pmr: That is what I suspect too, but I really would like to understand why. Naively, it does seem exactly like what an xvalue should be to me. – Xoph Sep 24 '14 at 09:31
  • It is true that it might be an optimization, but it surely affects the semantic of the program itself, i.e. changing ownership. IIRC there has been a lot of discussion on this topic during the first standard drafts – Marco A. Sep 24 '14 at 09:38
  • The compiler is just being safe, in this case it would indeed work but it seems that the compiler didn't use the information "b won't be used again" when making the optimization while std::move tells him explicitely – meneldal Sep 24 '14 at 09:42
  • Related to [Is default constructor elision / assignment elision possible in principle?](http://stackoverflow.com/q/18609968/1708801) – Shafik Yaghmour Sep 24 '14 at 09:55
  • Am I missing something? The second one does a move and the first one does a copy, so the second one should be faster. `std::move(b)` is an xvalue, `b` is not – M.M Sep 24 '14 at 12:19
  • @MattMcNabb yes, and my question was why the compiler isn't allowed to automatically convert b into an xvalue, seeing that its scope is ending anyway. – Xoph Sep 24 '14 at 13:59

2 Answers2

7

Compilers can't break the as-if rule

As §1.9/1 states:

The semantic descriptions in this International Standard define a parameterized nondeterministic abstract machine. This International Standard places no requirement on the structure of conforming implementations. In particular, they need not copy or emulate the structure of the abstract machine. Rather, conforming implementations are required to emulate (only) the observable behavior of the abstract machine as explained below

i.e. a compiler can't change the observable behavior of the program. Automatically (even if with no repercussions) converting an assignment to a move assignment would break this statement.

Copy elisions can slightly alter this behavior, but that is regulated by §12.8/31.

If you want to use the move version, you'll have to explicitly ask for it as in the latter example.

Marco A.
  • 43,032
  • 26
  • 132
  • 246
  • 1
    OK, so in particular the programmer should have reliable copy/move operator/ctor calling. I somehow assumed it would be sensible to require the programmer to make these operations compatible semantically. I guess both approaches would have their pros and cons, but I can see why the standard sees it differently. – Xoph Sep 24 '14 at 09:53
  • 2
    In this specific code it wouldn't break the as-if rule because there is no output – M.M Sep 24 '14 at 14:02
5

Let's look at the next sample (please ignore void return type from operator=):

#include <iostream>

struct helper
{
    void operator=(helper&&){std::cout<<"move"<<std::endl;}
    void operator=(const helper&){std::cout<<"copy"<<std::endl;}
};

void fun()
{
    helper a;
    {
        helper b;
        a = b;
    }
}

void gun()
{
    helper a;
    {
        helper b;
        a = std::move(b);
    }
}
int main()
{
    fun();
    gun();
}

The operator= has different behaviour depending on its arguments. The compiler is allowed to optimise the code only if it is able to maintain the observable behaviour the same.

Considering b from fun an xvalue, while it isn't an xvalue at the moment of the call it will change the observable behaviour of the program and this is not desired nor allowed by the standard.

Mircea Ispas
  • 20,260
  • 32
  • 123
  • 211
  • Thanks, I was aware of the change in the constructor calling. I assumed that the programmer was somehow required to make move constructors/operators and copy constructors/operators "semantically matching", and I guess that's my flaw here! – Xoph Sep 24 '14 at 09:50