28

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.

Community
  • 1
  • 1
Kyle Strand
  • 15,941
  • 8
  • 72
  • 167
  • I think setCallback should get parameter by value rather than rvalue reference, am I wrong? – Slava Sep 09 '15 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. – Kyle Strand Sep 09 '15 at 18:31

2 Answers2

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • ....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`? – Kyle Strand Sep 09 '15 at 18:35
  • @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 Sep 09 '15 at 18:36
  • @Barry there are move only std::functions alternatives already out there just to mention two: https://github.com/Naios/Function2 and https://github.com/potswa/cxx_function – Naios Sep 09 '15 at 18:44
  • @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. – Kyle Strand Sep 09 '15 at 18:59
  • 1
    @KyleStrand Oh sure, that's fine. Moving the lambda around is perfectly fine. – Barry Sep 09 '15 at 19:11
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

Community
  • 1
  • 1
Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • 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. – Kyle Strand Sep 09 '15 at 18:38
  • 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 Sep 30 '15 at 04:25
  • @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). – Kyle Strand Aug 24 '16 at 17:58