Краткий пример:
#include <iostream>
int main()
{
int n;
[&](){n = 10;}(); // OK
[=]() mutable {n = 20;}(); // OK
// [=](){n = 10;}(); // Error: a by-value capture cannot be modified in a non-mutable lambda
std::cout << n << "\n"; // "10"
}
Вопрос: зачем нам mutable
ключевое слово? Это сильно отличается от традиционной передачи параметров в именованные функции. Что обоснование позади?
У меня сложилось впечатление, что весь смысл захвата по значению состоит в том, чтобы позволить пользователю изменить временное - иначе мне почти всегда лучше использовать захват по ссылке, не так ли?
Какие-нибудь просветления?
(Я использую MSVC2010, кстати. AFAIK это должно быть стандартным)
const
по умолчанию!const
по умолчанию.Ответы:
Это требует,
mutable
потому что по умолчанию объект функции должен выдавать один и тот же результат при каждом вызове. В этом разница между объектно-ориентированной функцией и функцией, эффективно использующей глобальную переменную.источник
void f(const std::function<int(int)> g)
. Как я могу гарантировать, чтоg
это на самом деле ссылочный прозрачный ?g
Вmutable
любом случае поставщик мог бы использовать . Так что я не буду знать. С другой стороны, если по умолчанию отличноconst
, и люди должны добавитьconst
вместоmutable
к объектам функции, компилятор может реально обеспечить соблюдениеconst std::function<int(int)>
части , и теперьf
можно предположить , чтоg
этоconst
, нет?Ваш код почти эквивалентен этому:
Таким образом, вы можете думать о лямбдах как о генерации класса с оператором (), который по умолчанию равен const, если только вы не скажете, что он изменчив.
Вы также можете рассматривать все переменные, захваченные внутри [] (явным или неявным образом), как члены этого класса: копии объектов для [=] или ссылки на объекты для [&]. Они инициализируются, когда вы объявляете лямбду, как если бы был скрытый конструктор.
источник
const
илиmutable
лямбда выглядела бы, если бы реализовывались как эквивалентные пользовательские типы, вопрос (как в заголовке и разработан OP в комментариях) почемуconst
по умолчанию, так что это не отвечает на него.Вопрос в том, "почти" ли это? Частым случаем использования является возврат или передача лямбд:
Я думаю, что
mutable
это не случай "почти". Я считаю, что «захват по значению», как «позволяет мне использовать его значение после смерти захваченного объекта», а не «позволяет мне изменить его копию». Но, возможно, с этим можно поспорить.источник
const
? Какую цель он достигает?mutable
кажется , неуместны здесь, когдаconst
это не по умолчанию в «почти» (: P) все остальное языка.const
было по умолчанию, по крайней мере, люди были бы вынуждены учитывать правильность const: /const
чтобы они могли вызывать его независимо от того, является ли лямбда-объект постоянным. Например, они могли бы передать это функции, принимающейstd::function<void()> const&
. Чтобы позволить лямбде изменять свои захваченные копии, в первоначальных работах элементы данных замыкания были определеныmutable
автоматически внутри. Теперь вам нужно вручную ввестиmutable
лямбда-выражение. Я не нашел подробного обоснования, хотя.FWIW, Херб Саттер, известный член комитета по стандартизации C ++, дает другой ответ на этот вопрос в вопросах правильности и удобства использования Lambda :
Его статья о том, почему это должно быть изменено в C ++ 14. Это краткое, хорошо написанное, заслуживающее прочтения, если вы хотите знать, «что умы [члена комитета]» касаются этой конкретной функции.
источник
Вам нужно подумать, каков тип закрытия вашей лямбда-функции. Каждый раз, когда вы объявляете лямбда-выражение, компилятор создает тип замыкания, который представляет собой не что иное, как объявление класса без имени с атрибутами ( среда, в которой было объявлено выражение Lambda) и
::operator()
реализованный вызов функции . Когда вы захватываете переменную с использованием копирования по значению , компилятор создает новыйconst
атрибут в типе замыкания, поэтому вы не можете изменить его внутри лямбда-выражения, потому что это атрибут «только для чтения», поэтому они Назовите это « закрытием », потому что каким-то образом вы закрываете свое лямбда-выражение, копируя переменные из верхней области в область лямбды.mutable
захваченная сущность станетnon-const
Атрибут вашего типа закрытия. Это то, что заставляет изменения, сделанные в изменяемой переменной, захваченной значением, не распространяться в верхнюю область, а оставаться внутри сохраняющей состояние лямбды. Всегда пытайтесь представить себе тип получающегося замыкания лямбда-выражения, которое мне очень помогло, и я надеюсь, что оно может помочь и вам.источник
См. Этот проект в разделе 5.1.2 [expr.prim.lambda], подраздел 5:
Редактировать комментарий Литба: Может, они думали о захвате по значению, чтобы внешние изменения переменных не отражались внутри лямбды? Ссылки работают в обе стороны, так что это мое объяснение. Не знаю, хорошо ли это, хотя.
Редактировать комментарий kizzx2: В большинстве случаев использование лямбды является функтором алгоритмов. Ness по умолчанию
const
позволяет использовать его в постоянной среде, точно так же, какconst
там могут использоваться обычные функции, а не-const
квалифицированные - нет. Возможно, они просто подумали, чтобы сделать это более интуитивным для тех случаев, которые знают, что происходит в их уме. :)источник
const
по умолчанию? Я уже получил новую копию, кажется странным не позволять мне ее менять - особенно это не является принципиально неправильным - они просто хотят, чтобы я добавилmutable
.var
бы ключевое слово, разрешающее изменение, и постоянное значение по умолчанию для всего остального. Сейчас нет, поэтому мы должны жить с этим. IMO, C ++ 2011 вышли довольно неплохо, учитывая все.n
это не временное. n является членом лямбда-функции-объекта, который вы создаете с помощью лямбда-выражения. По умолчанию ожидается, что вызов вашей лямбды не изменяет ее состояние, поэтому он предотвращает случайное изменениеn
.источник
Вы должны понять, что означает захват! это захват не передачи аргументов! давайте посмотрим на некоторые примеры кода:
Как видите, даже если значение
x
было изменено,20
лямбда по-прежнему возвращает 10 (x
все еще находится5
внутри лямбды). Изменениеx
внутри лямбды означает изменение самой лямбды при каждом вызове (лямбда мутирует при каждом вызове). Для обеспечения корректности в стандарт введеноmutable
ключевое слово. Определяя лямбду как изменчивую, вы говорите, что каждый вызов лямбды может вызвать изменение самой лямбды. Давайте посмотрим на другой пример:Вышеприведенный пример показывает, что, делая лямбду мутабельной, изменение
x
внутри лямбды «мутирует» лямбда при каждом вызове с новым значением,x
которое не имеет ничего общего с фактическим значениемx
в основной функцииисточник
В настоящее время есть предложение облегчить необходимость
mutable
в лямбда-объявлениях: n3424источник
mutable
это даже ключевое слово в C ++.Чтобы расширить ответ Puppy, лямбда-функции должны быть чистыми функциями . Это означает, что каждый вызов с уникальным входным набором всегда возвращает один и тот же результат. Давайте определим входные данные как набор всех аргументов плюс все захваченные переменные при вызове лямбды.
В чистых функциях вывод зависит исключительно от ввода, а не от какого-то внутреннего состояния. Поэтому любая лямбда-функция, если она чистая, не нуждается в изменении своего состояния и поэтому является неизменной.
Когда лямбда захватывает по ссылке, запись в захваченных переменных является проблемой для концепции чистой функции, потому что все, что должна делать чистая функция, - это возвращать вывод, хотя лямбда не обязательно мутирует, потому что запись происходит с внешними переменными. Даже в этом случае правильное использование подразумевает, что если лямбда вызывается с тем же самым вводом снова, вывод будет одинаковым каждый раз, несмотря на эти побочные эффекты для переменных by-ref. Такие побочные эффекты являются всего лишь способами возврата некоторого дополнительного ввода (например, обновления счетчика) и могут быть преобразованы в чистую функцию, например, возвращая кортеж вместо одного значения.
источник