Move Capture in Lambda

Move capture in lambda

Generalized lambda capture in C++14

In C++14 we will have the so called generalized lambda capture. This enables move capture. The following will be legal code in C++14:

using namespace std;

// a unique_ptr is move-only
auto u = make_unique<some_type>( some, parameters );

// move the unique_ptr into the lambda
go.run( [ u = move(u) ] { do_something_with( u ); } );

Also note if you need to move object from lambda to some other function you need to make lambda mutable.

go.run( [ u = move(u) ] mutable { do_something_with( std::move(u) ); } );

The generalized lambda capture is much more general in the sense that captured variables can be initialized with anything like so:

auto lambda = [value = 0] mutable { return ++value; };

In C++11 this is not possible yet, but with some tricks that involve helper types. Fortunately, the Clang 3.4 compiler already implements this awesome feature. The compiler will be released December 2013 or January 2014, if the recent release pace will be kept.

UPDATE: The Clang 3.4 compiler was released on 6 Jan 2014 with the said feature.

A workaround for move capture

Here's an implementation of a helper function make_rref which helps with artificial move capture

#include <cassert>
#include <memory>
#include <utility>

template <typename T>
struct rref_impl
{
rref_impl() = delete;
rref_impl( T && x ) : x{std::move(x)} {}
rref_impl( rref_impl & other )
: x{std::move(other.x)}, isCopied{true}
{
assert( other.isCopied == false );
}
rref_impl( rref_impl && other )
: x{std::move(other.x)}, isCopied{std::move(other.isCopied)}
{
}
rref_impl & operator=( rref_impl other ) = delete;
T && move()
{
return std::move(x);
}

private:
T x;
bool isCopied = false;
};

template<typename T> rref_impl<T> make_rref( T && x )
{
return rref_impl<T>{ std::move(x) };
}

And here's a test case for that function that ran successfully on my gcc 4.7.3.

int main()
{
std::unique_ptr<int> p{new int(0)};
auto rref = make_rref( std::move(p) );
auto lambda =
[rref]() mutable -> std::unique_ptr<int> { return rref.move(); };
assert( lambda() );
assert( !lambda() );
}

The drawback here is that lambda is copyable and when copied the assertion in the copy constructor of rref_impl fails leading to a runtime bug. The following might be a better and even more generic solution because the compiler will catch the error.

Emulating generalized lambda capture in C++11

Here's one more idea, on how to implement generalized lambda capture. The use of the function capture() (whose implementation is found further down) is as follows:

#include <cassert>
#include <memory>

int main()
{
std::unique_ptr<int> p{new int(0)};
auto lambda = capture( std::move(p),
[]( std::unique_ptr<int> & p ) { return std::move(p); } );
assert( lambda() );
assert( !lambda() );
}

Here lambda is a functor object (almost a real lambda) which has captured std::move(p) as it is passed to capture(). The second argument of capture is a lambda which takes the captured variable as an argument. When lambda is used as a function object, then all arguments that are passed to it will be forwarded to the internal lambda as arguments after the captured variable. (In our case there are no further arguments to be forwarded). Essentially, the same as in the previous solution happens. Here's how capture is implemented:

#include <utility>

template <typename T, typename F>
class capture_impl
{
T x;
F f;
public:
capture_impl( T && x, F && f )
: x{std::forward<T>(x)}, f{std::forward<F>(f)}
{}

template <typename ...Ts> auto operator()( Ts&&...args )
-> decltype(f( x, std::forward<Ts>(args)... ))
{
return f( x, std::forward<Ts>(args)... );
}

template <typename ...Ts> auto operator()( Ts&&...args ) const
-> decltype(f( x, std::forward<Ts>(args)... ))
{
return f( x, std::forward<Ts>(args)... );
}
};

template <typename T, typename F>
capture_impl<T,F> capture( T && x, F && f )
{
return capture_impl<T,F>(
std::forward<T>(x), std::forward<F>(f) );
}

This second solution is also cleaner, because it disables copying the lambda, if the captured type is not copyable. In the first solution that can only be checked at runtime with an assert().

Move capture of parameter in lambda

Move semantics only work on mutable objects. You will need to make your lambda mutable:

auto func_lambda = [b = std::move(b)]() mutable {
f(std::move(b));
};

However you can only call such lambda once. If you want to call it multiple time, you'll have to generate values or use std::exchange(b, B{})

Passing a lambda with moved capture to function

The error happens because your lambda has non-copyable captures, making the lambda itself not copyable. std::function requires that the wrapped object be copy-constructible.

If you have control over call_func, make it a template:

template<typename T>
void call_func(T&& func)
{
func();
}

int main()
{
std::fstream fs{"test.txt", std::fstream::out};
auto lam = [fs = std::move(fs)] { const_cast<std::fstream&>(fs).close(); };
call_func(lam);
}

Following is my take on your idea in (2). Since std::function requires the wrapped object to be copy-constructible, we can make our own function wrapper that does not have this restriction:

#include <algorithm>
#include <fstream>
#include <iterator>
#include <utility>
#include <memory>
#include <sstream>
#include <vector>

template<typename T>
void call_func(T&& func) {
func();
}

// All functors have a common base, so we will be able to store them in a single container.
struct baseFunctor {
virtual void operator()()=0;
};

// The actual functor is as simple as it gets.
template<typename T>
class functor : public baseFunctor {
T f;
public:
template<typename U>
functor(U&& f)
: f(std::forward<U>(f))
{}
void operator()() override {
f();
}
};

// In C++17 you don't need this: functor's default constructor can already infer T.
template<typename T>
auto makeNewFunctor(T&& v) {
return std::unique_ptr<baseFunctor>(new functor<T>{std::forward<T>(v)});
}

int main() {
// We need to store pointers instead of values, for the virtual function mechanism to behave correctly.
std::vector<std::unique_ptr<baseFunctor>> functors;

// Generate 10 functors writing to 10 different file streams
std::generate_n(std::back_inserter(functors), 10, [](){
static int i=0;
std::ostringstream oss{"test"};
oss << ++i << ".txt";
std::fstream fs{oss.str(), std::fstream::out};
return makeNewFunctor([fs = std::move(fs)] () mutable { fs.close(); });
});

// Execute the functors
for (auto& functor : functors) {
call_func(*functor);
}
}

Note that the overhead from the virtual call is unavoidable: Since you need functors with different behavior stored in the same container, you essentially need polymorphic behavior one way or the other. So you either implement this polymorphism by hand, or use virtual. I prefer the latter.

lambda move capture for std::thread

template<class Function, class... Args, class Rep, class Period>
void Timer::setInterval(const std::chrono::duration<Rep, Period>& sleep_duration, Function&& f, Args&&... args) {
this->clear = false;
std::thread t([this, f=std::move(f), sleep_duration, args = std::make_tuple(std::forward<Args>(args)...))]() {
while(true) {
if (this->clear) return;
std::this_thread::sleep_for(sleep_duration);
if(this->clear) return;
std::apply(f, args);
}
});
t.detach();
}

That should do correct capturing. Note that as the timer goes off more than once, you don't want to further-forward data into the f.

But that still doesn't work because you didn't capture this.

Really, you should do a proper cv/mutex and not leak threads.

class Timer {
std::condition_variable cv;
mutable std::mutex m;
bool clear = false;
std::thread active_thread;
void dispose_thread() {
if (active_thread.joinable()) {
stop();
active_thread.join();
}
clear = false; // safe, because all threads are dead
}
public:
template<class Function, class... Args, class Rep, class Period>
[[maybe_unused]] void setInterval(const std::chrono::duration<Rep, Period>& sleep_duration, Function&& f, Args&&... args);
auto lock() const {
return std::unique_lock( m );
}
void stop() {
auto l = lock();
clear = true;
cv.notify_all();
}
~Timer() { dispose_thread(); }
};

template<class Function, class... Args, class Rep, class Period>
void Timer::setInterval(const std::chrono::duration<Rep, Period>& sleep_duration, Function&& f, Args&&... args) {
dispose_thread();
// capture everything explicitly; when messing with threads,
// best to understand state
active_thread = std::thread(
[this, f = std::move(f), sleep_duration, args = std::make_tuple(std::forward<Args>(args)...)]()
{
while(true) {
{
auto l = lock();
// return value here isn't useful, but the function is:
(void)cv.wait_for(l, sleep_duration, [&]{ return clear; });
if (clear)
return;
}
std::apply(f, args);
}
}
);
}

and .stop() and .join() in the destructor.

Live example.

This prevents you from leaking threads and outliving your timer object and doesn't make you wait for a timer interval to set a new interval and the like.

You should also set up sleep_until time points, as if you want to do something every second and the f function takes 0.1 seconds, this will actually repeat every 1.1 seconds.

If you want to maintain multiple worker threads in one Timer object, I'd switch to using std::futures in a vector, and maybe sweeping them to clean up ones that are finished at various intervals.

The idea of waiting for existing tasks to cleanup before starting a new tasks has value to me. If you don't like that, you could have the threads report back that they are ready to clean up, and clean them up lazily later. But this also requires multiple clear variables, so the threads that are delayed don't get the wrong value when the next thread is ready to go.

Moving a lambda: once you've move-captured a move-only type, how can the lambda be used?

You can move the lambda, that's fine. That's not what your problem is though, you're trying to instantiate a std::function with a noncopyable lambda. And the:

template< class F > 
function( F f );

constructor of function does:

5) Initializes the target with a copy of f.

This is because std::function:

satisfies the requirements of CopyConstructible and CopyAssignable.

Since function has to be copyable, everything you put into it must also be copyable. And a move-only lambda does not meet that requirement.

Why is a lambda not-movable if it captures a not-copiable object using std::move()?

Your lambda does not become non-movable by having a move capture. But it does become non-copyable, which is a problem.

std::function does not support moving the supplied functor into itself, it always does a copy. Non-copyable lambdas (and other callables) therefore cannot be used with std::function. The reason for this limitation is that the standard requires std::function to be copyable, which could not be achieved if it was initialised with a non-copyable callable.

Capture and move a unique_ptr in a c++14 lambda expression

The operator () of a lambda is const by default, and you can't move from a const object.

Declare it mutable if you want to modify the captured variables.

auto lambda = [ capturedStr = std::move(str) ] () mutable {
// ^^^^^^^^^^
cout << *capturedStr.get() << endl;
auto str2 = std::move(capturedStr);
};

C++ `std::move` custom type into a lambda capture

The problem isn't with the lambda. E.g. this works:

auto l = [&, capA{std::move(n)}](int) mutable -> int {
int b = *capA.pNum; // 5
return a + b; // 8
};

The problem is with std::function and this lambda. Because you capture a non-copyable object then the lambda is non-copyable. std::function requires its stored object to be copyable so that's why it doesn't work.

You can read more here: Move-only version of std::function

Timing of lambda expression move capture

The evaluation order in C++17 is well-defined, such that the expression leading to the function call (tim->async_wait) is sequenced before any of its arguments.

C++14 however, this is unspecified, due to lack of such sequencing rules. That is, it may work, it may not, and implementations aren't required to tell you which way it picks, or even to be consistent from one call to another.

cannot pass deleted lambda function with move?

Let's start with a the error message:

function "lambda []std::string (int c, int y) mutable->std::string<unnamed>(const lambda []std::string (int x, int y) mutable->std::string &)" (declared implicitly) cannot be referenced -- it is a deleted function

That's a mouthful. It's more understandable if you recognize that lambda []std::string (int c, int y) mutable->std::string is a name for the type of your lambda (apparently its return statement causes the auto to be deduced as std::string). Let's call that type L and see what the message looks like.

function "L(const L &)" (declared implicitly) cannot be referenced -- it is a deleted function

Your lambda's copy constructor is implicitly deleted. That should make sense since your lambda has captured a non-copyable value (the unique_ptr). And yet, you try to make a copy when you pass personLambda by value to acceptLambda. This does not work.

The full error message probably has more information that could help point you to seeing why your lambda cannot be copied. After the error line are probably some note lines explaining that unique_ptr cannot be copied. When using an IDE, such as Visual Studio, you might have to switch from the summary compiler output tab to the full compiler output tab to see this, though.

Option 1

You might be able to pass the lambda by reference. Might. Maybe you do need a copy in the larger scheme of things, but if you don't, then acceptLambda could take its second argument by reference (and probably should take the first argument by const reference).

void acceptLambda(const std::vector<int> & myVector, Func & f) 
// ^-- by reference

Option 2

You could get around being unable to copy your lambda the same way you got around being unable to copy your unique_ptr. You could move it. Just be aware that personLambda will not be usable after the call to acceptLambda. (If this is a problem, you likely should be using a shared_ptr instead of a unique_ptr.)

acceptLambda(myVector, std::move(personLambda));
// ^^^^^^^^^^ ^


Related Topics



Leave a reply



Submit