0

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.

Nobilis
  • 7,310
  • 1
  • 33
  • 67

0 Answers0