Лямбда используется по ссылке 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
После того, как я удалил символ &, текущий результат не изменился. Я думаю, что между ними должна быть разница, но я не знаю, какая!
И вы не берете i по ссылке, используйте auto count{ [&]() { std::cout << ++i << '\n'; } };
@PepijnKramer, хотя он будет генерировать вывод 1 2 3
, это будет только путем изменения i
в main
, и я не думаю, что это то, чего пытался достичь ОП. Вначале я тоже шел в этом направлении, но потом подумал, что ОП хотел бы использовать член i
лямбды, не затрагивая i
из main
. Мой ответ показывает, как это сделать, а также ответ Мэтта.
@ZheCi - можете ли вы уточнить, намеревались ли вы изменить только член i
лямбды (не затрагивая i
в main
)?
Да, моя цель - изменить i в лямбде, мне все равно, изменять ли i в main.
Ваша лямбда count
захватывает i
копированием. Для захвата i
по ссылке нужно
auto count{ [&i]() mutable {
std::cout << ++i << '\n';
} };
Протестируйте в прямом эфире на Coliru.
Как отмечено в комментарии, вам не нужно mutable
в лямбде при захвате по ссылке.
Если вы захватываете по ссылке, изменяемый объект не нужен;)
Это будет работать, изменив i
в файле main. Я считаю, что ОП хотел добиться этого, изменив только член i
лямбда. Это показано в моем и другом ответе.
Вы пытаетесь 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 Да, это правильно.
Объявите 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()
.
Ваш
std::function
получает копию экземпляра лямбда.