29

This answer explains how to move-capture a variable within a lambda in C++14.

But once you've move-captured an un-copyable object (such as a std::unique_ptr) within a lambda, you cannot copy the lambda itself.

This would be fine if you could move the lambda, but I get a compile error when trying to do so:

using namespace std;

class HasCallback
{
  public:
    void setCallback(std::function<void(void)>&& f)
    {
      callback = move(f);
    }

    std::function<void(void)> callback;
};

int main()
{
  auto uniq = make_unique<std::string>("Blah blah blah");
  HasCallback hc;
  hc.setCallback(
      [uniq = move(uniq)](void)
      {
        std::cout << *uniq << std::endl;
      });

  hc.callback();
}

This produces the following error with g++ (I've attempted to copy only the relevant line):

error: use of deleted function ‘main()::<lambda()>::<lambda>(const main()::<lambda()>&’

...implying, I think, that my attempt to move the lambda has failed.

clang++ gives a similar error.

I tried explicitly moveing the lambda (even though it's a temporary value), but that did not help.

EDIT: The answers below adequately address the compile errors produced by the above code. For an alternate approach, simply release the unique pointer's target value into a std::shared_ptr, which can be copied. (I'm not writing this as an answer, because that would assume that this is an XY problem, but the underlying reason why unique_ptr can't be used in a lambda that gets converted to a std::function is important to understand.)

EDIT 2: Hilariously enough, I just realized auto_ptr would actually do the right thing here (!), as far as I can tell. It acts essentially like unique_ptr, but allows copy-construction in place of move-construction.

2
  • I think setCallback should get parameter by value rather than rvalue reference, am I wrong? Commented Sep 9, 2015 at 18:30
  • @Slava That's what I originally had, but it gave the same error. I thought taking the rvalue reference would permit (/force) the lambda to be move-constructed, but that doesn't seem to be the case. Commented Sep 9, 2015 at 18:31

2 Answers 2

25

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.

Sign up to request clarification or add additional context in comments.

5 Comments

....huh. Thank you-- I was having a very difficult time parsing those error messages even for this simple case. Is there any way to change the signature to work, short of using a universal-ref-qualified template parameter instead of std::function?
@KyleStrand It doesn't matter what the parameter is, you just can't construct the function. If you need something type-erased, you'd have to write a movable function equivalent.
@Barry there are move only std::functions alternatives already out there just to mention two: github.com/Naios/Function2 and github.com/potswa/cxx_function
@Barry I meant using the lambda directly (without converting it to function or any other similar type) by writing a template function that would use the lambda itself as the template type.
@KyleStrand Oh sure, that's fine. Moving the lambda around is perfectly fine.
18

std::function is not a lambda! It's a wrapper that can be constructed from any type of callable, including a lambda. std::function requires that the callable be copy-constructible, which is why your example fails.

A move-only lambda can be moved again as shown below.

template<typename F>
void call(F&& f)
{
    auto f1 = std::forward<F>(f);  // construct a local copy
    f1();
}

int main()
{
  auto uniq = make_unique<std::string>("Blah blah blah");
  auto lambda = [uniq = move(uniq)]() {
        std::cout << *uniq << std::endl;
      };
//  call(lambda);   // doesn't compile because the lambda cannot be copied
  call(std::move(lambda));
}

Live demo

3 Comments

Ack. I knew std::function is not a lambda, but since lambda is convertible to std::function I wasn't keeping the distinction clear in my mind.
So how to implement the HasCallback in original code? What I mean is, how to save the move-only lambda into a move only container or move only data struct?
@alpha You would either need to use the lambda directly without converting it to another type (i.e. the function that calls a lambda would need to take the lambda as a template-type argument), or use a different function-class (there are various alternatives to std::function in the wild, or you could design your own).

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.