Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How to move variables captured by lambda function

70 views
Skip to first unread message

Gene Bushuyev

unread,
Jul 22, 2011, 8:42:31 PM7/22/11
to

As a follow up to a previous discussion (http://groups.google.com/
group/comp.lang.c++.moderated/browse_thread/thread/d22909b22e3798d3),
I was trying to solve the problem of lambda function not being able to
move the variables it captures. You can either specify the capture by
value or by reference, but cannot tell lambda to move, so for example
in the function call below, unnecessary copies of 'a' are created:

std::function<void()> f(A a)
{
return [a]() { std::cout << "\nlambda"; };
}

or even worse:

std::function<void()> f(const A& a)
{
return [a]() { std::cout << "\nlambda"; };
}

So I came up with "class mover", which moves parameter in its non-
const copy constructor:

template<class T>
class mover
{
T t;
public:
template<class Arg>
mover(Arg&& a) : t(std::forward<Arg>(a)) {}
mover(const mover<T>& m) : t(m.t)
{ std::cout << "\nwarning: mover copy ctor"; }
mover(mover<T>& m) : t(std::move(m.t)) {}
mover(mover<T>&& m) : t(std::move(m.t)) {}

const T& get() const { return t; }
T& get() { return t; }
};

This seem to work, not creating any copies of A:

std::function<void()> mf(mover<A> m)
{
return [m]() { std::cout << "\nlambda"; };
}

A test case can be found here: http://ideone.com/XIkyL

The question is whether this solution has some serious drawbacks and
whether there are cases when this doesn't work. I for one, cannot get
rid of "mover(const mover<T>& m)" and have to rely upon compiler
eliding this copy ctor.


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Daniel Krügler

unread,
Jul 25, 2011, 5:57:53 PM7/25/11
to

Am 23.07.2011 02:42, schrieb Gene Bushuyev:
>
> As a follow up to a previous discussion (http://groups.google.com/
> group/comp.lang.c++.moderated/browse_thread/thread/d22909b22e3798d3),
> I was trying to solve the problem of lambda function not being able to
> move the variables it captures. You can either specify the capture by
> value or by reference, but cannot tell lambda to move, so for example
> in the function call below, unnecessary copies of 'a' are created:
>
> std::function<void()> f(A a)
> {
> return [a]() { std::cout<< "\nlambda"; };
> }

This is an example, where I can agree upon.

> or even worse:
>
> std::function<void()> f(const A& a)
> {
> return [a]() { std::cout<< "\nlambda"; };
> }

I don't see a reason for an unnecessary copy in this example. Obviously there should be exactly one copy and this is necessary given the function parameter a.

> So I came up with "class mover", which moves parameter in its non-
> const copy constructor:
>
> template<class T>
> class mover
> {
> T t;
> public:
> template<class Arg>
> mover(Arg&& a) : t(std::forward<Arg>(a)) {}
> mover(const mover<T>& m) : t(m.t)
> { std::cout<< "\nwarning: mover copy ctor"; }
> mover(mover<T>& m) : t(std::move(m.t)) {}
> mover(mover<T>&& m) : t(std::move(m.t)) {}
>
> const T& get() const { return t; }
> T& get() { return t; }
> };

Yes, I have seen similar suggestions by others as a library work-around for the current restrictions of lambda expressions.

> This seem to work, not creating any copies of A:
>
> std::function<void()> mf(mover<A> m)
> {
> return [m]() { std::cout<< "\nlambda"; };
> }

I don't understand why you make this wrapper approach part of the API of mf(). It should be sufficient to declare a function-local variable of automatic storage duration to realize the same thing like so:

std::function<void()> f(A a)
{

mover<A> ma(std::move(a));
return [ma]() { std::cout << "\nlambda"; };
}

Using the wrapper this way has two further advantages:

1) The move is visually recognizable, matching the mental model of the core language where the move of an lvalue should generally be visually visible. I'm not mentioning the special "lvalue-as-rvalue" consideration of 12.8 p31+32 here, because they would not apply here.

2) You don't need the extra immutable copy constructor at all.

> A test case can be found here: http://ideone.com/XIkyL
>
> The question is whether this solution has some serious drawbacks and
> whether there are cases when this doesn't work. I for one, cannot get
> rid of "mover(const mover<T>& m)" and have to rely upon compiler
> eliding this copy ctor.

I would stay away from using your mover type as part of function signatures. It should be used as locally as possible to reduce the risk of silent and unexpected moves.

HTH & Greetings from Bremen,

Daniel Kr�gler

Gene Bushuyev

unread,
Jul 26, 2011, 8:34:35 AM7/26/11
to

On Jul 25, 2:57 pm, Daniel Krügler <daniel.krueg...@googlemail.com>
wrote:

> Am 23.07.2011 02:42, schrieb Gene Bushuyev:
[snip]

I agree, this solution is both cleaner and safer, but it comes at the
expense of an extra move. Granted, moves are supposed to be cheap. The
problem only arises when a class doesn't have a move constructor, or
move has to copy (e.g. small string optimization). In this case
changing the signature of the function to `f(mover<A>)` produces
exactly the same number of copies as the original function `f(A)`, but
if added to the body of the function there will be an extra copy.
What's the solution for generic function, if we don't know anything
about class A? E.g.

template<class A>


std::function<void()> f(A a)
{

mover<A> ma(std::move(a)); // extra copy if A has no move


return [ma]() { std::cout << "\nlambda"; };
}

SG

unread,
Jul 26, 2011, 8:34:52 AM7/26/11
to

On 23 Jul., 02:42, Gene Bushuyev wrote:
> As a follow up to a previous discussion,

> I was trying to solve the problem of lambda function not being able to
> move the variables it captures. You can either specify the capture by
> value or by reference, but cannot tell lambda to move

The work-around I came up with was to combine a lambda with std::bind
since bind does honor the "rvalueness" of arguments during binding.
Depending on value category, a parameter is either copied or moved
into the function object. And I expect the resulting callable type to
provide a reasonable move ctor.

> so for example
> in the function call below, unnecessary copies of 'a' are created:
>
> std::function<void()> f(A a)
> {
> return [a]() { std::cout << "\nlambda"; };
> }

which could be rewritten like this:

std::function<void()> f(A a)
{

return bind([](A const&a){ std::cout << "\nlambda"; },move(a));
}

> or even worse:
>
> std::function<void()> f(const A& a)
> {
> return [a]() { std::cout << "\nlambda"; };
> }

What's worse about this one? The lambda still stores a copy of the A-
object (and not just a reference).

> So I came up with "class mover", which moves parameter in its non-
> const copy constructor:
>
> template<class T>
> class mover
> {
> T t;
> public:
> template<class Arg>
> mover(Arg&& a) : t(std::forward<Arg>(a)) {}
> mover(const mover<T>& m) : t(m.t)
> { std::cout << "\nwarning: mover copy ctor"; }
> mover(mover<T>& m) : t(std::move(m.t)) {}
> mover(mover<T>&& m) : t(std::move(m.t)) {}
>
> const T& get() const { return t; }
> T& get() { return t; }
> };

Hmmm... that makes mover an irregular type (w.r.t. copy semantics)
doesn't it? I know that this is what you designed it to be but I would
try to avoid that. We just deprecated std::auto_ptr. ;-)

Cheers!
SG

0 new messages