Используйте лямбда через ссылку std::function, вы не можете изменить значение в лямбда

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

#include <iostream>
#include <functional>

void myInvoke(const std::function<void()>& fn)
{
    fn();
}

int main()
{
    int i{ 0 };

    // Increments and prints its local copy of @i.
    auto count{ [i]() mutable {
      std::cout << ++i << '\n';
    } };

    myInvoke(count);
    myInvoke(count);
    myInvoke(count);

    return 0;
}

Фактические результаты:

1
1
1

Ожидаемые результаты:

1
2
3

После того, как я удалил символ &, текущий результат не изменился. Я думаю, что между ними должна быть разница, но я не знаю, какая!

Ваш std::function получает копию экземпляра лямбда.

tkausl 04.02.2023 15:00

И вы не берете i по ссылке, используйте auto count{ [&]() { std::cout << ++i << '\n'; } };

Pepijn Kramer 04.02.2023 15:02

@PepijnKramer, хотя он будет генерировать вывод 1 2 3, это будет только путем изменения i в main, и я не думаю, что это то, чего пытался достичь ОП. Вначале я тоже шел в этом направлении, но потом подумал, что ОП хотел бы использовать член i лямбды, не затрагивая i из main. Мой ответ показывает, как это сделать, а также ответ Мэтта.

wohlstad 04.02.2023 15:22

@ZheCi - можете ли вы уточнить, намеревались ли вы изменить только член i лямбды (не затрагивая i в main)?

wohlstad 04.02.2023 16:07

Да, моя цель - изменить i в лямбде, мне все равно, изменять ли i в main.

ZheCi 05.02.2023 00:04
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
5
177
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ваша лямбда count захватывает iкопированием. Для захвата i по ссылке нужно

auto count{ [&i]() mutable {
  std::cout << ++i << '\n';
} };

Протестируйте в прямом эфире на Coliru.

Как отмечено в комментарии, вам не нужно mutable в лямбде при захвате по ссылке.

Если вы захватываете по ссылке, изменяемый объект не нужен;)

Pepijn Kramer 04.02.2023 15:03

Это будет работать, изменив i в файле main. Я считаю, что ОП хотел добиться этого, изменив только член i лямбда. Это показано в моем и другом ответе.

wohlstad 04.02.2023 16:06
Ответ принят как подходящий

Вы пытаетесь myInvoke принять функцию fn по ссылке, но переданная вами лямбда count не является std::function.
Каждый раз, когда вы вызываете myInvoke со своей лямбдой, она преобразуется в std::function посредством создания временного объекта (копии). Это то, на что ссылается fn.
Так что i будет изменен на копии и не повлияет на вашу count лямбду.
Вот почему принятие fn по значению ведет себя аналогично.

Один из способов справиться с этим — сделать myInvoke шаблоном функции, где fn — аргумент шаблона (типа T &), поэтому преобразование выполнять не нужно.

Пример кода:

#include <iostream>
#include <functional>

template <typename T>
void myInvoke(T & fn)
{
    fn();
}

int main()
{
    int i{ 0 };

    // Increments and prints its local copy of @i.
    auto count = [i]() mutable {
      std::cout << ++i << '\n';
    };

    myInvoke(count);
    myInvoke(count);
    myInvoke(count);
}

Выход:

1
2
3

Альтернативным способом было бы сделать count типа std::function<void()>, как это было предложено в другом ответе @Matt.
Это также предотвратит создание временных копий для каждого вызова, поскольку fn сможет привязываться к нему напрямую.

Примечание:
Еще один упомянутый вариант — изменить лямбду так, чтобы она захватывала i по ссылке, а не по значению:

//------------vv------------
auto count = [&i]() { ... };

Это выведет 1 2 3, как вы ожидаете, но только потому, что все копии функций фактически изменят i в стеке main.
Я думал, что это не то, что вы хотели, а скорее увеличение только i члена лямбды.

Я думаю, вы сделали очень важный вывод: lambdas не являются std::function Поскольку lambda не является std::function, при выполнении передачи параметров (const std::function<void()> &fn = lambda), std::function <void()> будет сгенерирован в соответствии с лямбда-выражением для устранения несоответствия типов. Проблема Верно ли мое понимание выше? Спасибо за ваш ответ

ZheCi 05.02.2023 00:38

@ZheCi Да, это правильно.

wohlstad 05.02.2023 14:07

Объявите count как std::function<void()> и удалите const из параметра myInvoke, тогда все будет работать как положено:

#include <functional>
#include <iostream>

void myInvoke(std::function<void()> &fn) {
  fn();
}

int main() {
  int i{ 0 };

  // Increments and prints its local copy of @i.
  std::function<void()> count{[i]() mutable {
    std::cout << ++i << '\n';
  }};

  myInvoke(count);
  myInvoke(count);
  myInvoke(count);

  return 0;
}

Выход:

1
2
3

Ответ @wohlstad дает хорошее объяснение того, почему вам нужно объявить count как std::function<void()> вместо auto, чтобы предотвратить создание временного объекта при вызове myInvoke().

Кстати, если вы замените [i] на [i = 0], вам даже не нужно объявлять int i в main().

Другие вопросы по теме