Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does the compiler know to move local variables?

I'm curious as to exactly how this feature works. Consider something like

std::unique_ptr<int> f() { std::unique_ptr<int> lval(nullptr); return lval; }

This code compiles fine even for a move-only type, as the compiler implicitly moves it. But logically, for any return expression, determining whether or not the result refers to a local variable would be solving the Halting Problem- and if the compiler simply treated all local variables as rvalues in the return expression, then this would be problematic as the variable may be referred to in that one expression multiple times. Even if a local only had one direct reference, you would not be able to prove that it did not have other indirect aliases.

So how does the compiler know when to move from the return expression?

like image 965
Puppy Avatar asked Jul 15 '12 14:07

Puppy


2 Answers

There's a simple rule: If the conditions for copy elision are met (except that the variable may be function parameter), treat as rvalue. If that fails, treat as lvalue. Otherwise, treat as lvalue.

§12.8 [class.copy] p32

When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. —end note ]

Example:

template<class T>
T f(T v, bool b){
  T t;
  if(b)
    return t; // automatic move
  return v; // automatic move, even though it's a parameter
}

Not that I personally agree with that rule, since there is no automatic move in the following code:

template<class T>
struct X{
  T v;
};

template<class T>
T f(){
  X<T> x;
  return x.v; // no automatic move, needs 'std::move'
}

See also this question of mine.

like image 159
Xeo Avatar answered Nov 06 '22 22:11

Xeo


The Standard does not say "refers to a local variable", but it says "is the name of a non-volatile automatic object".

§12.8 [class.copy] p31

[...] This elision of copy/move operations, called copy elision, is permitted in the following circumstances [...]:

  • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object [...]

So, pointers or references are not allowed. If you read it in evil ways that seek to exploit an interpretation that is less likely to have been intended, you can say that it means to move the following

struct A {
  std::unique_ptr<int> x;
  std::unique_ptr<int> f() { return x; }
};

int main() { A a; a.f(); }

In this case, the return expression is the name of a variable with automatic storage duration. Some other paragraphs in the Standard can be interpreted in multiple ways, but the rule is to take the interpretation that is most likely to be intended.

like image 30
Johannes Schaub - litb Avatar answered Nov 06 '22 23:11

Johannes Schaub - litb