Перемещение лямбда: как только вы переместили захваченный тип только для перемещения, как можно использовать лямбда?

Этот ответ объясняет, как перемещать-захватывать переменную в лямбда в С++ 14.

Но как только вы перенесли захваченный объект (например, std::unique_ptr), который не скопирован в лямбда, вы не можете скопировать сам лямбда.

Это было бы хорошо, если бы вы могли перемещать лямбда, но при попытке сделать это я получаю ошибку компиляции:

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();
}

Это вызывает следующую ошибку с g++ (я попытался скопировать только соответствующую строку):

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

... подразумевая, я думаю, что моя попытка переместить лямбда не удалась.

clang++ дает аналогичную ошибку.

Я пробовал явно move включить лямбда (хотя это временное значение), но это не помогло.

EDIT: В нижеприведенных ответах адекватно рассматриваются ошибки компиляции, создаваемые приведенным выше кодом. Для альтернативного подхода просто release уникальное целевое значение указателя в std::shared_ptr, которое можно скопировать. (Я не пишу это как ответ, потому что это предполагает, что это проблема XY, но основная причина, по которой unique_ptr не может использоваться в лямбда, которая преобразуется в std::function, важна для понимания.)

РЕДАКТИРОВАТЬ 2: Как ни странно, я просто понял, что auto_ptr будет делать правильные вещи здесь (!), насколько я могу судить. Он действует, по существу, как unique_ptr, но позволяет создавать копии вместо конструкции перемещения.

Ответы

Ответ 1

Вы можете переместить лямбда, это прекрасно. Это не ваша проблема, но вы пытаетесь создать экземпляр std::function с помощью некрупной лямбды. И:

template< class F > 
function( F f );

конструктор function делает:

5) Инициализирует цель с помощью копии f.

Это связано с тем, что std::function:

удовлетворяет требованиям CopyConstructible и CopyAssignable.

Так как function должен быть скопирован, все, что вы вкладываете в него, также должно быть скопируемым. И лямбда только для движения не отвечает этому требованию.

Ответ 2

std::function не лямбда! Это оболочка, которая может быть построена из любого типа вызываемых, включая лямбда. std::function требует, чтобы мог быть выполнен с возможностью копирования, поэтому ваш пример не работает.

Перемещение только лямбда может быть перемещено снова, как показано ниже.

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));
}

Живая демонстрация