#опытным
Лямбда выражения имеют одну интересную особенность. И эта особенность аффектит то, что можно делать внутри лямбды.
Простой пример:
int val = 0;
auto lambda1 = [&val]() { std::cout << ++val << std::endl; };
auto lambda2 = [val]() { std::cout << ++val << std::endl; };
Определяем 2 лямбды: в одну захватываем
val по ссылке, во второй - по значению.В чем здесь проблема?
А в том, что во втором случае мы получим ошибку компиляции.
На самом деле operator() у замыкания по умолчанию помечен как const метод, видимо чтобы его можно было вызывать на константных объектах замыкания. То есть это значит, что мы не можем изменять поля замыкания при вызове лямбды.
Ссылки интересным образом это ограничение обходят. Так как ссылки сами по себе неизменяемы(так как по факту это обертка над константным указателем), то формально требования выполняются. А то, что мы изменяем объект, на который указывает ссылка - "вы не понимаете, это другое".
Под одним из прошлых постов разгорелась дискуссия по этому моменту. @KpacHoe_ympo в этом комменте упомянул, что в константных методах можно менять объекты, на которые ссылаются ссылки. Однако на мой вгляд(и подтверждений в стандарте я не нашел), что это не уб. Иначе в лямбду нельзя было бы захватывать ссылки вообще. Вряд ли весь захват по ссылке в лямбду держится на уб.
А вот объекты, захваченные по значению, не умеют обходить ограничения константности. В замыкании они превращаются в обычные поля класса, которые нельзя изменять внутри константных методов.
Но если нам очень нужно изменять захваченные по значению поля? На помощь приходит уже полюбившийся нам mutable. Лямбду можно пометить этим ключевым словом и тогда ее константный оператор() перестанет быть константным! Тогда мы можем как угодно изменять любые захваченные значения:
int val = 0;
auto lambda2 = [val]() mutable { std::cout << ++val << std::endl; };
Теперь все работает отлично.
То есть в лямбда выражениях mutable используется в случаях, когда необходима модификация объектов, захваченных по значению.
Это может использоваться, например, для перемещения захваченных объектов в one-shot коллбэках:
auto callback = [message=get_message, &scheduler]() mutable {
// some preparetions
scheduler.submit(std::move(message));
}
SomeTask task{callback};
task.run();По завершению таски, коллюэк кладет в шедулер мувнутое сообщения без накладных расходов на копирование.
Мутабельные лямбды - не такая популярная фича, еще менее известная, чем обычный mutable, но о их существовании нужно знать.
Break the rules. Stay cool.
#cppcore