returned lambda with value capture: move should be used if the variable is about to go out of scope.

223 views
Skip to first unread message

Mikhail Semenov

unread,
Jun 5, 2013, 6:18:39 AM6/5/13
to std-pr...@isocpp.org
I wonder if there is a chance to get it expicitly stated in the standard that, in  cases like the one below, the move constructor (or even higher optimization, like re-use) should be used (not the copy constructor):
auto f()
{
    double a[] = {0.1, 2.0, 3.5};
    std::vector<double>v1(a,a+3);
    return [v1](){ return v1; };    // use move (or re-use) not copy
}
 
I have noticed that some developers started writing their own captures, which explicitly use move.
At the same time, some compilers (like Visual C++) already provide optimization. 
I think the users should not re-invent the wheel.
 
 

Daniel Krügler

unread,
Jun 5, 2013, 6:51:36 AM6/5/13
to std-pr...@isocpp.org
2013/6/5 Mikhail Semenov <mikhailse...@gmail.com>:
> I wonder if there is a chance to get it expicitly stated in the standard
> that, in cases like the one below, the move constructor (or even higher
> optimization, like re-use) should be used (not the copy constructor):

It is unclear which copy/move constructor you are referring to. A
copy/move constructor of the vector or of the lambda closure? If of
the vector, do you mean it should be invoked when captured or during
the closure return statement? In the following I assume you mean that
of the vector and you mean the return statement of the closure.

Second, assuming I understood your request correctly, this has not
anything to do with optimization, it would be a fundamental core
language change and I would expect that to be controversial (see below
why). What the language allows you since the Bristol meeting is to
support "expression captures" (normative wording denotes them as
"init-capture"), so you can indeed rewrite the lambda expression now
to

auto f()
{
double a[] = {0.1, 2.0, 3.5};
std::vector<double>v1(a,a+3);
return [v1 = std::move(v1)](){ return v1; }; // use move (or
re-use) not copy
}

The effect is that now the local variable v1 is moved into a data
member of the closure named v1 within the scope of the lambda
expression.

> auto f()
> {
> double a[] = {0.1, 2.0, 3.5};
> std::vector<double>v1(a,a+3);
> return [v1](){ return v1; }; // use move (or re-use) not copy
> }
>
> I have noticed that some developers started writing their own captures,
> which explicitly use move.
> At the same time, some compilers (like Visual C++) already provide
> optimization.
> I think the users should not re-invent the wheel.

It all depends what precisely you mean. If you mean the compiler
should move the v1 capture in your example code during the return
statement of the closure I'm strongly opposed to such a change: A
closure is a function object that can potentially be called multiple
times. If it would move the *own* member (and not some local variable
with automatic storage duration), this would violate the model of RVO
and it would lead to a lot of surprises for the user of that code.

- Daniel

DeadMG

unread,
Jun 5, 2013, 7:26:21 AM6/5/13
to std-pr...@isocpp.org
I think that he wants to implicitly move the local variable into the lambda. This I support, as I think that the language can easily specify the limited and known correct situations under which this can occur.

Daniel Krügler

unread,
Jun 5, 2013, 7:32:20 AM6/5/13
to std-pr...@isocpp.org
2013/6/5 DeadMG <wolfei...@gmail.com>:
> I think that he wants to implicitly move the local variable into the lambda.
> This I support, as I think that the language can easily specify the limited
> and known correct situations under which this can occur.

I disagree with that being a good change. RVO applies to situations
where we have a clear "exit" channel and after which the affected
object is "history". But this isn't provided for situations where
expressions or variables are captured by a closure. IMO the new
init-captures that I have presented for this case

return [v1 = std::move(v1)](){ return v1; };

allow to support this request without leading to unexpected gotchas in
the code parts following the closure construction.

- Daniel

[P.S. Please quote a minimum of the message that you are responding
to. In 80% of your replies I don't understand to *what* or to "whom*
you reply]

Mikhail Semenov

unread,
Jun 5, 2013, 7:48:28 AM6/5/13
to std-pr...@isocpp.org
Daniel,
 
>return [v1 = std::move(v1)](){ return v1; };
 
This will work, as I wanted it to.
 
My point is that
return [v1 = std::move(v1)](){ return v1; };
should be equivalent to this
return [v1](){ return v1; };
when v1 is a local variable.
 
>It all depends what precisely you mean. If you mean the compiler
>should move the v1 capture in your example code during the return
>statement of the closure I'm strongly opposed to such a change: A
>closure is a function object that can potentially be called multiple
>times. If it would move the *own* member (and not some local variable
>with automatic storage duration), this would violate the model of RVO
>and it would lead to a lot of surprises for the user of that code.
 
But I meant only in case when the local variable is at the end of its existence,
(there is no member variable!). There no surprises here:
 
The lambda captures the local variable and is returned from the block.
I mean when the variable is captured by lambda ([v1]()...): move should be done, not copy.
 
Sorry for the confusion.
 
Mikhail.
 
 
 
 
 

DeadMG

unread,
Jun 5, 2013, 7:51:48 AM6/5/13
to std-pr...@isocpp.org
RVO applies to situations where we have a clear "exit" channel

We have a clear exit channel- into the lambda.

 and after which the affected object is "history".

Since it is a local variable, this applies just as much to trying to put it in a lambda as trying to put it anywhere else, really.

unexpected gotchas in the code parts following the closure construction.

There are no code parts following the closure construction, since it is a return statement.

Frankly, I don't understand why the Standard does not permit RVO anywhere the compiler can prove it under as-if, where a move is seen as a valid as-if optimization of a copy, and all copy and move constructors are considered elidable in all scenarios that the compiler can prove. But that's a story for another day, perhaps. The lambda deal should certainly be available within the existing philosophy.

Mikhail Semenov

unread,
Jun 5, 2013, 8:02:12 AM6/5/13
to std-pr...@isocpp.org
Daniel,
 
I support DeadMG on this: clear cases of optimization should be properly stated in the standard: the same as with local return from a block (when move is done). Otherwise, we may get unexpected surprises in cases of cross-compilation, when some compilers don't optimise the code properly and use a copy in the above-mentioned case.
 
Mikhail.
 

Daniel Krügler

unread,
Jun 5, 2013, 8:02:47 AM6/5/13
to std-pr...@isocpp.org
2013/6/5 Mikhail Semenov <mikhailse...@gmail.com>:
> Daniel,
>
>>return [v1 = std::move(v1)](){ return v1; };
>
> This will work, as I wanted it to.
>
> My point is that
> return [v1 = std::move(v1)](){ return v1; };
> should be equivalent to this
> return [v1](){ return v1; };
> when v1 is a local variable.

Thanks for clarification. As I responded to DeadMG, I would be opposed
to such a change. Implicit move operations are fine in situations
where the moved object is no longer of interest. This is OK in a
function return statement acting on a variable with automatic storage
duration. It is (in general) *not* OK when constructing a closure
object, because this might just be one part of the operations within
that function and the following code could validly refer to v1 again.
I this situation an *implicit* move would be very contrary to the
general behaviour of C++ and it would have possibly surprising effects
on the rest of the code within this function.

- Daniel

Ville Voutilainen

unread,
Jun 5, 2013, 8:04:37 AM6/5/13
to std-pr...@isocpp.org
On 5 June 2013 14:51, DeadMG <wolfei...@gmail.com> wrote:
RVO applies to situations where we have a clear "exit" channel

We have a clear exit channel- into the lambda.

Except for cases where the local variable is passed elsewhere in addition to the lambda.
 

Jonathan Wakely

unread,
Jun 5, 2013, 8:23:32 AM6/5/13
to std-pr...@isocpp.org
The proposed change would allow this to compile:

std::function<int()> func()
{
  auto p = std::make_unique<int>();
  return [p]{ return *p; };
}

but it would fail to compile if slightly modified:

std::function<int()> func()
{
  auto p = std::make_unique<int>();
  auto lambda = [p]{ return *p; };
  std::cout << "loggity loggity log\n";
  return lambda;
}

The difference is a bit subtle and could be confusing.

Jonathan Wakely

unread,
Jun 5, 2013, 8:29:12 AM6/5/13
to std-pr...@isocpp.org

That's an extreme example where the change causes it to fail to compile, a nastier example would be where the captured variable is both copyable and movable, and the change introduces a performance hit because a move silently becomes an expensive copy.  The explicit [p = std::move(p)] capture avoids that problem.

I'm not sure this problem should skupper the proposal, but I find it concerning.

Mikhail Semenov

unread,
Jun 5, 2013, 8:43:56 AM6/5/13
to std-pr...@isocpp.org
My view on this is that the use std::move should be avoided as much as possible to avoid complexity of the code, except in cases when you explicitly
use rvalue references: T&& (usually in parameters).  As I mentioned before, we already deal with cases of "implicit" move when a local variable is returned as a value of the function; why not do it here?
 

DeadMG

unread,
Jun 5, 2013, 10:09:59 AM6/5/13
to std-pr...@isocpp.org
it would fail to compile if slightly modified:

We already have an identical situation with function return values.

std::unique_ptr<int> f(std::unique_ptr<int> in) { return in; }
std::unique_ptr<int> func() {
    auto p = std::make_unique<int>();
    return p; // Fine

    auto var = p;
    std::cout << "loggity loggity log\n";
    return var; // Not fine.
}

When dealing with move-only types this is just one of the things you have to deal with. In both cases you can always use an explicit std::move.

Except for cases where the local variable is passed elsewhere in addition to the lambda.

It's gonna be UB for them to access that variable after the lambda is constructed and the function returns, and they can't access it whilst the lambda is being constructed because, y'know, the lambda is of a strict form which does not permit that. In the example

std::function<int()> func()
{
  auto p = std::make_unique<int>();
  f(p); // assume reference, say
  return [p]{ return *p; };
}

There is no way for anyone to legally access p during or after the lambda's construction. The lambda code obviously does not do so (and including something that does would obviously fall outside the remit of the proposal), and anyone accessing it through an alias after the lambda is constructed is already in UB-land because the function has returned before any more user-defined code can execute. But finally, again, there is no difference here between "Move p into return value" and "Move p into lambda and move lambda into return value". If you want to suggest that there is, perhaps you should provide a code sample which shows some bad behaviour, because as far as I can tell, it is strictly limited such that that is impossible, just like the existing implicit move.

Jonathan Wakely

unread,
Jun 5, 2013, 10:44:45 AM6/5/13
to std-pr...@isocpp.org


On Wednesday, June 5, 2013 3:09:59 PM UTC+1, DeadMG wrote:
it would fail to compile if slightly modified:

We already have an identical situation with function return values.

std::unique_ptr<int> f(std::unique_ptr<int> in) { return in; }
std::unique_ptr<int> func() {
    auto p = std::make_unique<int>();
    return p; // Fine

    auto var = p;

This line would fail, not the return.
 
    std::cout << "loggity loggity log\n";
    return var; // Not fine.
}

[...]
 
But finally, again, there is no difference here between "Move p into return value" and "Move p into lambda and move lambda into return value".

I disagree, they're not the same.

Why should lambdas be special? If it works for lambdas it would work here too:

int deref(std::unique_ptr<int> p) { return *p; }


int func()
{
  auto p = std::make_unique<int>();
  return deref(p);
}

Or:

struct A { std::unique_ptr<int> p; };

A func()

{
  auto p = std::make_unique<int>();
  return { p };
}


Can we please stop trying to squeeze every possible language feature into lambdas?
Not everything can or should be done with lambdas, especially if it makes the rest of the language into second-class citizens.

Ville Voutilainen

unread,
Jun 5, 2013, 10:51:31 AM6/5/13
to std-pr...@isocpp.org
On 5 June 2013 17:09, DeadMG <wolfei...@gmail.com> wrote:
Except for cases where the local variable is passed elsewhere in addition to the lambda.

It's gonna be UB for them to access that variable after the lambda is constructed and the function returns, and they can't access it whilst the lambda is being constructed because, y'know, the lambda is of a strict form which does not permit that. In the example

std::function<int()> func()
{
  auto p = std::make_unique<int>();
  f(p); // assume reference, say
  return [p]{ return *p; };
}



Sure. But do you think it should be different to have


std::function<int()> func()
{
  auto p = std::make_unique<int>();
  return [p]{ return *p; };
}

in contrast to

std::function<int()> func()
{
  auto p = std::make_unique<int>();
  auto x = [p]{ return *p; };
  return x;
}

and then


std::function<int()> func()
{
  auto p = std::make_unique<int>();
  auto x = [p]{ return *p; };
  f(p);
  return x;
}

The difference between the first case and the others is quite subtle.

DeadMG

unread,
Jun 5, 2013, 11:02:29 AM6/5/13
to std-pr...@isocpp.org
Why should lambdas be special? If it works for lambdas it would work here too

Because the language guarantees what is going to happen. If you have `deref(p)`, the compiler cannot know that moving p is safe. After all, deref might use a global variable, set earlier, that happens to alias p, and if the content of p were to magically disappear, this would break the user's code. On the other hand, it can do this when moving into a lambda because the language guarantees the consequences of constructing a lambda and the elision rules for move and copy constructors gives us the freedom to ignore such aliases. Nobody can write their move constructor to depend on global aliases to their objects, because we already elide them in other contexts. deref is another matter. I think that arguably, we could have an attribute that would permit deref(p), and I think we should widen the scope of elision, but that's beyond the scope of this discussion. The simple fact is that the move/copy constructors have different semantics to just any arbitrary user function.

And also, this doesn't make lambdas special, it makes them the same. It brings them in line with what we already do. We can have the requisite guarantees about move and copy constructors in this case, so it should be legal for the compiler to elide and required for them to implicitly move, just like in every other place we can have those guarantees.

 This line would fail, not the return.

Yeah, I made a slight labelling mistake there, but my sample and Jonathan's sample both fail at exactly the same line, and for exactly the same reason- unique_ptr is move only so you can't prepare the return value ahead of the return statement if you wish to use an implicit move. This is just as true for returning a unique_ptr directly right now as it is for returning a lambda containing a unique_ptr if this proposal were accepted.

Sure. But do you think it should be different to have

Replace [p] { return *p; } with p, and you have exactly the same issues. This doesn't bring anything new to the table.

Ville Voutilainen

unread,
Jun 5, 2013, 11:14:52 AM6/5/13
to std-pr...@isocpp.org
On 5 June 2013 18:02, DeadMG <wolfei...@gmail.com> wrote:

Sure. But do you think it should be different to have

Replace [p] { return *p; } with p, and you have exactly the same issues. This doesn't bring anything new to the table.




No I don't. If I return p;, it'll always move, and it'll never move surreptitiously before I call other functions, whereas
an automatic move-capture in a lambda that's not immediately returned will do precisely that.

DeadMG

unread,
Jun 5, 2013, 11:16:05 AM6/5/13
to std-pr...@isocpp.org
You cannot call other functions between the move and the return. How could that possibly be done? The language guarantees that if you create a lambda, the contents will be copied (now moved), then you return. What could you possibly do to access the local variable after the move but before the return?

DeadMG

unread,
Jun 5, 2013, 11:16:36 AM6/5/13
to std-pr...@isocpp.org
Er, if you return a lambda. I obviously don't mean for some arbitrary lambda creation.

Ville Voutilainen

unread,
Jun 5, 2013, 11:19:35 AM6/5/13
to std-pr...@isocpp.org
On 5 June 2013 18:16, DeadMG <wolfei...@gmail.com> wrote:
Er, if you return a lambda. I obviously don't mean for some arbitrary lambda creation.




"Obviously". That's why I asked whether you really want [p] to mean move capture in
some situations, and copy-capture in others, depending on the surrounding context.
Or whether you want it to do the move in every case, or in none of the cases.

DeadMG

unread,
Jun 5, 2013, 11:29:48 AM6/5/13
to std-pr...@isocpp.org
That's why I asked whether you really want [p] to mean move capture in some situations, and copy-capture in others, depending on the surrounding context.

I thought this was pretty clear. 

We already have an identical situation with function return values. 

Amongst other things.

To be even more explicit about this, the only condition covered is of the form return lambda;, much like existing implicit moves is only return variable;. In addition, whilst the OP did not cover this, as far as I can see, it would also have to include no uniform captures (although you could employ some weasel wording for them if you really want which could be viable) only reference and copy/move/this. Therefore, 

std::function<int()> func()
{
  auto p = std::make_unique<int>();
  return [p]{ return *p; };
}

std::function<int()> func()
{
  auto p = std::make_unique<int>();
  auto x = [p]{ return *p; };
  return x;
}

std::function<int()> func()
{
  auto p = std::make_unique<int>();
  auto x = [p]{ return *p; };
  f(p);
  return x;
}

The latter two samples fail to compile because auto x = [p]{ return *p; }; is obviously not of the form return lambda so no implicit move can be applied and unique_ptr has no copy constructor. So as I previously stated, they behave exactly the same as if [p] { return *p; } was replaced with p (also the return type adjusted and std::function requires copyability.. but I get your meaning). The first sample compiles because return [p] { return *p; } is of the form return lambda so an implicit move can be applied. Again, this is exactly the same as if [p] { return *p; } was replaced with p.

Now I'm pretty sure we should all be on the same page.

Mikhail Semenov

unread,
Jun 5, 2013, 11:34:44 AM6/5/13
to std-pr...@isocpp.org
I think the bottom line is that
(1) lambdas are designed to capture the context; in this case when returned from a function they should MOVE the captured local values effortlessly;
(2) some compilers already optimize that automatically (which means that writing v1= std::move(v1) is just stating the obvious);
(3) [I said it before] the language already provides similar cases for returning local variable values.
 
 

Nevin Liber

unread,
Jun 5, 2013, 11:37:07 AM6/5/13
to std-pr...@isocpp.org
On 5 June 2013 09:44, Jonathan Wakely <c...@kayari.org> wrote:

Why should lambdas be special?
Can we please stop trying to squeeze every possible language feature into lambdas?
Not everything can or should be done with lambdas, especially if it makes the rest of the language into second-class citizens.

+1.

If it is a general optimization, it should also work for a named struct which does the same thing as a lambda.
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

DeadMG

unread,
Jun 5, 2013, 11:42:23 AM6/5/13
to std-pr...@isocpp.org
It should do, but implementing it in the general case is solving Halting Problem, so the real question is, "Where do we draw the line?". I, for one, think that "Core language construct" is a fine place to draw lines. If you can propose how it could be more general, that's great, but there's no reason to sacrifice more generality just because full generality is impossible.

Ville Voutilainen

unread,
Jun 5, 2013, 11:46:10 AM6/5/13
to std-pr...@isocpp.org
On 5 June 2013 18:29, DeadMG <wolfei...@gmail.com> wrote:

The latter two samples fail to compile because auto x = [p]{ return *p; }; is obviously not of the form return lambda so no implicit move can be applied and unique_ptr has no copy constructor. So as I previously stated, they behave exactly the same as if [p] { return *p; } was replaced with p (also the return type adjusted and std::function requires copyability.. but I get your meaning). The

Except that if you do that replacement, they will compile fine. :P
 
As an initial reaction, I guess I would be less opposed to this than Daniel. We could specify this in the same way
as moving-in-return, elision rules need to be applicable and there needs to be no intervening referencing
of the variable that is captured this way (we might not need to constrain it purely to returns). However, I
think Jonathan makes a very good point. And, given that you can move into a lambda with the c++14
init-captures, I am not convinced this change is worth its cost.

DeadMG

unread,
Jun 5, 2013, 11:53:43 AM6/5/13
to std-pr...@isocpp.org
Except that if you do that replacement, they will compile fine. :P

Er, if you do

auto f() {
    auto p = std::make_unique<int>();
    auto x = p;

I'm pretty sure you will get a compiler error. 

you can move into a lambda with the c++14 init-captures

You could also explicitly move a return value. That didn't stop us implementing it before. 

Mikhail Semenov

unread,
Jun 5, 2013, 11:53:52 AM6/5/13
to std-pr...@isocpp.org
> And, given that you can move into a lambda with the c++14
> init-captures, I am not convinced this change is worth its cost.
 
It's worth the clarity, specially for lambdas!
 

 

Ville Voutilainen

unread,
Jun 5, 2013, 12:02:16 PM6/5/13
to std-pr...@isocpp.org
On 5 June 2013 18:53, DeadMG <wolfei...@gmail.com> wrote:
Except that if you do that replacement, they will compile fine. :P

Er, if you do

auto f() {
    auto p = std::make_unique<int>();
    auto x = p;

I'm pretty sure you will get a compiler error. 

Ah, I stand corrected. With auto&&, the situation is different.


you can move into a lambda with the c++14 init-captures

You could also explicitly move a return value. That didn't stop us implementing it before. 



Fair enough. I still don't think it's a superior solution to an init-capture. It would be nice-to-have
since it would optimize cases where people don't explicitly move, so there's some benefit in the idea.



Róbert Dávid

unread,
Jun 5, 2013, 12:36:14 PM6/5/13
to std-pr...@isocpp.org


2013. június 5., szerda 18:02:16 UTC+2 időpontban Ville Voutilainen a következőt írta:



On 5 June 2013 18:53, DeadMG <wolfei...@gmail.com> wrote:
Except that if you do that replacement, they will compile fine. :P

Er, if you do

auto f() {
    auto p = std::make_unique<int>();
    auto x = p;

I'm pretty sure you will get a compiler error. 

Ah, I stand corrected. With auto&&, the situation is different.

 
Well you can compile that, but it won't be a unique_ptr<int>&&, just a unique_ptr<int>&, as "p" in the second line is not an rvalue...

Regards, Robert

Róbert Dávid

unread,
Jun 5, 2013, 12:49:19 PM6/5/13
to std-pr...@isocpp.org
This should not be lambda-specific, could be applied to all automatic variable:

std::vector<int> make_foo_vect();
void use_vect(std::vector<int> asd);
void f() {
  std
::vector<int> foo = make_foo_vect();
  use_vect
(foo); //copy
  use_vect
(foo); //copy
  use_vect
(foo); //move
}
A compiler can detect when is the last usage, where it can handle it as an rvalue. Even if it is not 'base' standard, can be made as an allowed optimization, just like copy elision.

I for now don't see any code this could break, have think about it a bit.

Regards, Robert

Ville Voutilainen

unread,
Jun 5, 2013, 1:13:51 PM6/5/13
to std-pr...@isocpp.org
Yep, and returning _that_ unique_ptr& will not move, so it will still fail to compile.

DeadMG

unread,
Jun 5, 2013, 2:09:38 PM6/5/13
to std-pr...@isocpp.org
@Robert: Try what happens when use_vect uses a hidden global reference to it's arguments, directly or indirectly.

The simple fact is that generalized moving is not possible. It is only possible in some very limited contexts. Lambdas are one of those contexts. Finding others will be non-trivial.

Richard Smith

unread,
Jun 5, 2013, 4:08:28 PM6/5/13
to std-pr...@isocpp.org
On Wed, Jun 5, 2013 at 11:09 AM, DeadMG <wolfei...@gmail.com> wrote:
@Robert: Try what happens when use_vect uses a hidden global reference to it's arguments, directly or indirectly.

The simple fact is that generalized moving is not possible. It is only possible in some very limited contexts. Lambdas are one of those contexts.

I'm sympathetic to the proposal. However, there are some corners where the behavior would break code, even for lambdas, and that concerns me. For example, if the lambda has multiple captures, and one of the captures' initializations looks at the other object:

auto g() {
  std::vector<int> v = {0};
  std::vector<int> &r = v;
  return [v, r] { return r[0]; };
}

This works if the implementation happens to capture 'r' before 'v', and fails otherwise. (You can get similar problems from a capture of a non-reference entity, but it requires a little more work.)

For the case where a return statement is of the form "return lambda-expression;", and the type of the lambda-expression is the return type of the function (which in practice means we're in C++14 and the declared return type is 'auto' or 'decltype(auto)'), and the lambda has a single capture, and that capture is a simple-capture, then I believe capturing by move is reasonable (no more or less broken than the existing special case for "return id-expression;"). But that seems like quite a special case, and if we have this at all, I think it should work for an arbitrary lambda-expression.

So I think the proposal needs more work, and needs someone to think carefully about the corner cases, and it needs a paper explaining the result of that careful thought and giving solid motivation for this change, but shouldn't be considered dead in the water. (As a start, if you require that in this case, you perform all init-captures before all by-copy simple-captures of references before all by-move simple-captures of non-references, you solve most -- but not all -- of the problems.)

DeadMG

unread,
Jun 5, 2013, 5:29:36 PM6/5/13
to std-pr...@isocpp.org
@Richard: That issue will have already been addressed by return id-expression, but more seriously, you are returning a reference to a local variable. This is UB anyway so I'm not too concerned about it, and I'm not sure how your example could be reworked to have a similar issue without referring to a local variable.

I am thinking something like, for each capture, if that capture is a simple-capture which captures a non-reference local variable, then capture by move.

Richard Smith

unread,
Jun 5, 2013, 7:36:24 PM6/5/13
to std-pr...@isocpp.org
On Wed, Jun 5, 2013 at 2:29 PM, DeadMG <wolfei...@gmail.com> wrote:
@Richard: That issue will have already been addressed by return id-expression, but more seriously, you are returning a reference to a local variable.

No, I'm not. When a lambda captures a reference by copy, it makes a copy -- the capture member is not of reference type.

"return id-expression;" doesn't suffer from this case (at least, not so badly) because it only moves *one* local object, so the moved-from state is only visible within destructors of other local variables and temporaries.
 
I am thinking something like, for each capture, if that capture is a simple-capture which captures a non-reference local variable, then capture by move.

That's not sufficient. In addition to the above problem, init-captures could look at the moved-from variable. As I said before, this needs some detailed analysis.

corn...@google.com

unread,
Jun 7, 2013, 12:13:55 PM6/7/13
to std-pr...@isocpp.org


On Wednesday, June 5, 2013 5:34:44 PM UTC+2, Mikhail Semenov wrote:
I think the bottom line is that
(1) lambdas are designed to capture the context; in this case when returned from a function they should MOVE the captured local values effortlessly;
(2) some compilers already optimize that automatically (which means that writing v1= std::move(v1) is just stating the obvious);


Really? Those compilers are overreaching. Copy elision is a special license granted by the standard to optimize as if "copy construct something and destroy the source" has no side effects, even if copy constructor and/or destructor actually do. Move-for-NRVO is yet another special license to optimize as if local variables referenced in a return statement cannot be referenced after the return value is constructed, even though destructors of other local variables can.

Basically, both optimizations can cause observable behavior to change, which is precisely why they are the only optimizations mentioned in the standard - every other optimization is covered by the as-if rule, but these aren't, so they need to be allowed specifically.

Extending move-for-NRVO to any other situation must be a change in the standard; a compiler cannot do it on its own and stay within the standard.

Mikhail Semenov

unread,
Jun 7, 2013, 3:14:20 PM6/7/13
to std-pr...@isocpp.org
Richard,
 
Thank you for the example. I suppose there can be another, probably more contrived one when object share some common data that is going to be moved.
I think without analysing further, it can be limited to cases when there is only one object of a compound class in the capture, and the rest may be only of fundamental types or enumerated types.
 
By the way, in your example, v could be dropped from capture because it is not used in the body of lambda. But we can always change the example to
return [v, r] { return v[0+r[0]; };
 
Mikhail.
 

Richard Smith

unread,
Jun 7, 2013, 7:46:39 PM6/7/13
to std-pr...@isocpp.org
On Fri, Jun 7, 2013 at 12:14 PM, Mikhail Semenov
<mikhailse...@gmail.com> wrote:
> Thank you for the example. I suppose there can be another, probably more
> contrived one when object share some common data that is going to be moved.
> I think without analysing further, it can be limited to cases when there is
> only one object of a compound class in the capture, and the rest may be only
> of fundamental types or enumerated types.

That's certainly possible. It's not obvious to me that it's the best
answer, and I hope that further analysis would yield a better one.

> By the way, in your example, v could be dropped from capture because it is
> not used in the body of lambda.

Sure, I was just trying to give a minimal example.

Mikhail Semenov

unread,
Jun 9, 2013, 7:35:22 AM6/9/13
to std-pr...@isocpp.org
Richard,
 
>That's certainly possible. It's not obvious to me that it's the best
>answer, and I hope that further analysis would yield a better one.
 
I suppose there could be an algorithmic solution. It depends how much we would like to assume.
For example if the moves are performed in the non-interleaved way (but in some implementation-dependent order),
and the empty function is available (empty(x) means that the movable part empty; we assume that there is only one movable part)
then we can solve the problem algorithmically (written in pseudo-code):

Safe move for multiple objects
 
e[0:n-1] empty states of the objects
s[0:n-1] source objects
t[0:n-1] target objects
 
for (int i=0; i < n; i++)
{
   e[i] = empty(s[i]);
}
for (int i = 0; i < n; i++)
{
   if (e[i]) { t[i] = copy(s[i]); continue; } // no difference between move and copy here
   if (empty(s[i])) // a situation: s[i] has become empty
   {
        for (int j = 0;  j < i;  j++)
        {
             if (e[j]) continue;
             s[j] = move(t[j]); // move backwards
             if (!empty(s[i])) // non-empty state is restored
             {
                  t[i] = copy(s[i]); // copy i-th
                  e[i] = true; // since we have copied, ignore in the future lookup
                  t[j] = move(s[j]); // move j-th as it was before
                  break; // leave the loop                 
             }
             else
             {
                  t[j] = move(s[j]); // move j-th as it was before
             }
        }       
   }
   else
   {
       t[i] = move(s[i]); // everything is fine, move the i-th element
   }
}
 
Mikhail.

Lawrence Crowl

unread,
Jun 9, 2013, 3:05:05 PM6/9/13
to std-pr...@isocpp.org
On 6/9/13, Mikhail Semenov <mikhailse...@gmail.com> wrote:
> > That's certainly possible. It's not obvious to me that it's
> > the best answer, and I hope that further analysis would yield
> > a better one.
>
> I suppose there could be an algorithmic solution. It depends
> how much we would like to assume. For example if the
> moves are performed in the non-interleaved way (but in some
> implementation-dependent order), and the *empty *function is
> available (empty(x) means that the movable part empty; we assume
> that there is only one movable part) then we can solve the problem
> algorithmically ...

The problem I see with the proposal is that it complicates the
programmers' views of the the source code. I would like to see
the effect on the reader of arbitrary code discussed in a paper.
In particular, if we "move into" a capture in order to "do the
right thing", then we have a situtation that lambda expressions
appear to have inconsist behavior. Can you reframe the proposal
so that we have a simpler view of the program?

--
Lawrence Crowl

Mikhail Semenov

unread,
Jun 9, 2013, 3:51:39 PM6/9/13
to std-pr...@isocpp.org
Lawrence,
 
The original proposal was that in cases like the following move should be used  not copy:
 
auto f()
{
    double a[] = {0.1, 2.0, 3.5};
    std::vector<double>v1(a,a+3);
    return [v1](){ return v1; };    // use move (or re-use) not copy
}
 
But there are cases for several captured values (noticed by Richard), which create problems:
 
auto g() {
  std::vector<int> v = {0};
  std::vector<int> &r = v;
  return [v, r] { return v[0]+r[0]; };
}
 
Depending on the order: either the value of v or r would not exist.
The simple solution here is to allow move only in cases when only one captured variable is a class object, the  rest are of fundamental types or
of enumerated  types (which means that only one variable can be moved). This is a very severe restriction, but is does the job.
 
In general, the objection (from Daniel Krügler) is that move (in view of N3648 "Generalized lambda capture") can be written explicitly:
auto g() {
  std::vector<int> v = {0};
  std::vector<int> &r = v;
  return [v = std::move(v), r] { return v[0]+r[0]; };
}
 
In this case v is moved, r is copied and no problem. But I still feel that, in cases when there is only one class object, move can be done automatically.
 
By the way, it can be extended to general function call as well at the end of a block:
 
auto f()
{
    double a[] = {0.1, 2.0, 3.5};
    std::vector<double>v1(a,a+3);
    return g(v1);    // use move (or re-use) not copy
}
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
My view is that it can be generalized to several captured parameters in general, but there are two approaches:
 
(1)  simple cases when there are references (basically the same values  are repeated),  the issue can be resolved automatically by comparing addresses of all the captured
variables involved and if the addresses are repeated the corresponding variables should be passed by value.
 
(2) cases when are memory overlaps (should we consider those?) can be resolved by adding an extra function empty() which can be accessed by the compiler to resolve
the issue (the algorithm showed in my previous e-mail). There may be more complicated overlapped cases with blocks of several elements (if used in move), which can be resolved slightly
differently if need be with an integer-value function non_empty_block_count().
_____________________________________________________________________________________________________________________________________________
I think to sum everything up: probably the best "shot" is a simple proposal when there is only one captured class object (whose name appears in the lambda body); the rest of the captured variables  are of fundamental types or enumerated types.  I think it's rather simple and does no require an extra effort or understanding.
 
Mikhail
 

 

DeadMG

unread,
Jun 10, 2013, 4:01:58 AM6/10/13
to std-pr...@isocpp.org
I think that strict aliasing has us covered here. There's no reason why you shouldn't have

f() {
    std::unique_ptr<int> x;
    std::unique_ptr<float> y;
    std::unique_ptr<int>& ref = x;
    return [x = std::move(x), y] { };
}

The compiler knows that, whilst x and ref might alias, neither of them can alias y, because that violates strict aliasing. So I would widen your description to copy only when legal aliasing might occur, and else, move non-reference local variables.

Mikhail Semenov

unread,
Jun 10, 2013, 7:21:59 AM6/10/13
to std-pr...@isocpp.org
>The compiler knows that, whilst x and ref might alias, neither of them can alias y, because that violates strict aliasing. So I would widen your >description to copy only when legal aliasing might occur, and else, move non-reference local variables.
 
That was exactly my point when I replied to Lawrence question
 
(1)  simple cases when there are references (basically the same values  are repeated),  the issue can be resolved automatically by comparing addresses of all the captured variables involved and if the addresses are repeated the corresponding variables should be passed by value.
(2) cases when are memory overlaps (should we consider those?)
________________________________________________________________________________________________
 
When strict aliases occur they can be easily recognised and all (but the last one of each group) should be copied.
 
 

Lawrence Crowl

unread,
Jun 11, 2013, 11:33:38 PM6/11/13
to std-pr...@isocpp.org
On 6/10/13, Mikhail Semenov <mikhailse...@gmail.com> wrote:
> > The compiler knows that, whilst x and ref might alias, neither
> > of them can alias y, because that violates strict aliasing. So
> > I would widen your description to copy only when legal aliasing
> > might occur, and else, move non-reference local variables.
>
> That was exactly my point when I replied to Lawrence question
>
> (1) simple cases when there are references (basically the same
> values are repeated), the issue can be resolved automatically by
> comparing addresses of all the captured variables involved and
> if the addresses are repeated the corresponding variables should
> be passed by value.
>
> (2) cases when are memory overlaps (*should we consider those?*)

I am not arguing that what you propose has no value. My concern is
the effect on the programmer trying to understand programs. Too many
special cases are hard on programmers. In that respect, either case
(1) or (2) might prove simpler once all the details are sorted out.

My guess is that the simplest rules would be an extension of the copy
elision rules. Doing so would likely also require a discussion of
any incompatibility. I am neither support nor opposing the idea,
I am only saying how I will evaluate the proposal once it is in
a paper. My hope is that we'll get a better paper as a result.

--
Lawrence Crowl

Mikhail Semenov

unread,
Jun 16, 2013, 11:57:43 AM6/16/13
to std-pr...@isocpp.org
I have attached my proposal.
MoveForLocalObjectsInLambda.pdf

Zhihao Yuan

unread,
Jun 16, 2013, 1:04:13 PM6/16/13
to std-pr...@isocpp.org
On Sun, Jun 16, 2013 at 11:57 AM, Mikhail Semenov
<mikhailse...@gmail.com> wrote:
>> I have attached my proposal.

Let me see...

auto f() {
std::vector<int> r { 1, 2, 3 };
return std::make_tuple([r] { return r[0]; }, [r] { return r[1]; });
}

This is my understanding of "If a lambda expression is used in
a return statement".

Anyway, the above is a joke. Here is my point:

auto f() {
std::unique_ptr<int> p;
return std::make_tuple(p);
}

This does not compile, but base on the logic of your proposal,
it should, it's nothing different from returning a lambda which
capturing one local variable.

But I hope not -- just keep the relationship between value categories
and expression types as simple as possible, programmer will thank
you.

--
Zhihao Yuan, ID lichray
The best way to predict the future is to invent it.
___________________________________________________
4BSD -- http://4bsd.biz/

Mikhail Semenov

unread,
Jun 16, 2013, 3:23:16 PM6/16/13
to std-pr...@isocpp.org
>But I hope not -- just keep the relationship between value categories
>and expression types as simple as possible, programmer will thank
>you.
I've noticed how "simple" things are, in general, especially about "odr-usage and const definitions".
You never know when to define extra things in the cpp-files if they appear in the h-files. And the
compilers differ as well.
 
 

Zhihao Yuan

unread,
Jun 16, 2013, 4:09:41 PM6/16/13
to std-pr...@isocpp.org
On Sun, Jun 16, 2013 at 3:23 PM, Mikhail Semenov
<mikhailse...@gmail.com> wrote:
> I've noticed how "simple" things are, in general, especially about
> "odr-usage and const definitions".

Please do not use the fact of the existence of a bad design
to prove the correctness of another bad design.
Reply all
Reply to author
Forward
0 new messages