Suppose I am in a function such that its interface accepts a particular object only by const
reference. In that same function I want to enqueue some task that will execute in the future and call some callback. Something like this:
void foo(const A &a)
{
auto callback = [a]() { processA(a); }; // passing "a" by value as "a" will go out of scope soon after returning
enqueueTask(callback); // execute at some point in the future
return;
}
Suppose also that processA
supports moving/forwarding and we want to take advantage of that to save ourselves an extra copy.
As it stands I believe a
will be copied twice - once captured by value in the lambda and once when passed to processA
since lambda captures by value are read-only, meaning processA(std::move(a));
moves nothing.
Now, what if I were clever and I cast the const
-ness away before passing a
to processA
in order to save myself a copy. Something like:
auto callback = [a]() { processA(std::move(const_cast<A&>(a))); };
This should work, I tried it out:
#include <iostream>
struct A
{
A() {}
A(const A& other) { std::cout << "copy" << std::endl; }
A(A&& other) { std::cout << "move" << std::endl; }
};
void processA(A a) { std::cout << "processing a " << &a << std::endl; } // printing its address in case it gets optimised away
int main()
{
const A a1;
[a1]() { processA(std::move(a1)); }(); // copied
const A a2;
[a2]() mutable { processA(std::move(a2)); }(); // copied
const A a3;
[a3]() { processA(std::move(const_cast<A&>(a3))); }(); // moved!
return 0;
}
It prints
copy
copy
processing ...
copy
copy
processing ...
copy
move
processing ...
Does moving a3
here count is undefined behaviour? I suspect it does, since it's moved into processA
and processA
is free to modify its parameters (modifying a const is undefined, I believe?). But here, I'm not modifying the original object, but the copy passed to the lambda. Or does that not matter since it's captured by the lambda as const
?
EDIT: Looks like having a mutable
qualifier together with a std::move
capture does the trick. Live example. The const_cast
must be UB but I'd be curious to learn if there's nuance there in how const
variables are captured in lambdas.