Лямбда функции c что это
Лямбда-функции в C++
Лямбда-функции появились в C++11. Они представляют собой анонимные функции, которые можно определить в любом месте программы, подходящем по смыслу.
Приведу пример простейшей лямбда функции:
Выражение auto myLambda означает объявление переменной с автоматически определяемым типом. Крайне удобная конструкция C++11, которая позволяет сделать ваш код более лаконичным и устойчивым к изменениям. Настоящий тип лямбда-функции слишком сложный, поэтому набирать его нецелесообразно.
Прежде, чем перейти к более сложным примерам лямбда-функций, определим вспомогательную шаблонную функцию:
Сначала просто выведем переданное лямбда-функции значение:
Теперь рассмотрим возможность «замыкания», т.е. передадим в лямбда-функцию значение локальной переменной по значению:
Но если мы хотим изменить значение переменной внутри лямбда-функции, то можем передать его по ссылке:
Обратите внимание на побочный эффект от связывания переменных с лямбда-функцией по ссылке:
Будьте особенно аккуратны с привязкой параметров по ссылке, когда работаете с циклами. Чтобы не получилось, что все созданные лямбда-функции работали с одним и тем же значением, когда должны иметь собственные копии.
Привязку можно осуществлять по любому числу переменных, комбинируя как передачу по значению, так и по ссылке:
Если требуется привязать сразу все переменные, то можно использовать следующие конструкции:
Допустимо и комбинирование:
Когда использовать лямбда-функции?
Конечно, этот аргумент не обязан быть лямбда-функцией, но часто их применение оказывается оправданным. Например:
Этот код интуитивно понятен, поэтому вы сами с ним легко разберетесь без моих пояснений. Приведу лишь то, что он выведет на консоль:
В своих программах вы тоже можете создавать универсальные функции, для которых управление ходом выполнения осуществляется с помощью функциональных объектов и лямбда-функций в частности.
Лямбда-выражения в C++
В C++ 11 и более поздних версиях лямбда-выражение, часто называемое лямбда– — это удобный способ определения объекта анонимной функции ( замыкания) непосредственно в расположении, где оно вызывается или передается в качестве аргумента функции. Обычно лямбда-выражения используются для инкапсуляции нескольких строк кода, передаваемых алгоритмам или асинхронным функциям. В этой статье определяются лямбда-выражения и их сравнение с другими методами программирования. Он описывает их преимущества и предоставляет некоторые основные примеры.
Связанные статьи
Части лямбда-выражения
В стандарте ISO C++ демонстрируется простое лямбда-выражение, передаваемое функции std::sort() в качестве третьего аргумента:
На следующем рисунке показана структура лямбда-выражения:
предложение Capture (также известное как оператор лямбда-выражения в спецификации C++).
список параметров Используемых. (Также называется лямбда-объявлением)
изменяемая спецификация Используемых.
Спецификация Exception Используемых.
замыкающий-возвращаемый тип Используемых.
Предложение Capture
Лямбда-выражение может добавлять новые переменные в тексте (в C++ 14), а также получать доступ к переменным из окружающей области или записыватьих. Лямбда-выражение начинается с предложения Capture. Он указывает, какие переменные фиксируются, а также указывает, является ли запись по значению или по ссылке. Доступ к переменным с префиксом амперсанда ( & ) осуществляется по ссылке и к переменным, к которым нет доступа по значению.
Пустое предложение фиксации ( [ ] ) показывает, что тело лямбда-выражения не осуществляет доступ к переменным во внешней области видимости.
Можно использовать режим захвата по умолчанию, чтобы указать, как фиксировать все внешние переменные, упоминаемые в теле лямбда-выражения: означает, что [&] все переменные, на которые вы ссылаетесь, захватываются по ссылке, а [=] значит, они записываются по значению. Можно сначала использовать режим фиксации по умолчанию, а затем применить для определенных переменных другой режим. Например, если тело лямбда-выражения осуществляет доступ к внешней переменной total по ссылке, а к внешней переменной factor по значению, следующие предложения фиксации эквивалентны:
При использовании записи по умолчанию фиксируются только те переменные, которые упоминаются в теле лямбда-выражения.
Захват, за которым следует многоточие, — это расширение пакета, как показано в следующем примере шаблона Variadic :
Чтобы использовать лямбда-выражения в теле функции члена класса, передайте this указатель в предложение Capture, чтобы предоставить доступ к функциям и членам данных включающего класса.
Visual Studio 2017 версии 15,3 и более поздних версий (доступно в режиме и более поздних версиях): this указатель может быть записан по значению путем указания *this в предложении capture. Захват по значению копирует весь замыкание на каждый узел вызова, где вызывается лямбда-выражение. (Замыканием является объект анонимной функции, инкапсулирующий лямбда-выражение.) Захват по значению полезен, когда лямбда выполняется в параллельных или асинхронных операциях. Это особенно полезно на некоторых аппаратных архитектурах, таких как NUMA.
Пример, демонстрирующий использование лямбда-выражений с функциями членов класса, см. в разделе «пример: использование лямбда-выражения в методе» в примерах лямбда-выражений.
При использовании предложения Capture рекомендуется учитывать такие моменты, особенно при использовании лямбда-выражений с многопоточностью:
Захваты ссылок можно использовать для изменения переменных вне, но захваты значений не могут. ( mutable позволяет изменять копии, но не оригиналы.)
Захват ссылок отражает обновления переменных вне, но не фиксирует значения.
Фиксация ссылки вводит зависимость от времени существования, тогда как фиксация значения не обладает зависимостями от времени существования. Это особенно важно при асинхронном выполнении лямбда-выражения. Если вы захватываете локальную по ссылке в асинхронном лямбда-выражении, это локально может быть легко пропала в момент выполнения лямбда-выражения. Код может вызвать нарушение прав доступа во время выполнения.
Обобщенная фиксация (C++14)
В C++14 вы можете объявлять и инициализировать новые переменные в предложении фиксации. Для этого не требуется, чтобы эти переменные существовали во внешней области видимости лямбда-функции. Инициализация может быть выражена в качестве любого произвольного выражения. Тип новой переменной определяется типом, который создается выражением. Эта функция позволяет собирать переменные только для перемещения (например, std::unique_ptr ) из окружающей области и использовать их в лямбда-выражении.
Список параметров
Лямбда-выражения могут записывать переменные и принимать входные параметры. Список параметров (лямбда-декларатор в стандартном синтаксисе) является необязательным и в большинстве аспектов напоминает список параметров для функции.
В C++ 14, если тип параметра является универсальным, можно использовать ключевое слово в качестве спецификатора типа. Это ключевое слово указывает компилятору создать оператор вызова функции в качестве шаблона. Каждый экземпляр auto в списке параметров эквивалентен отдельному параметру типа.
Лямбда-выражение может принимать другое лямбда-выражение в качестве своего аргумента. Дополнительные сведения см. в разделе «Лямбда-выражения более высокого порядка» в статье Примеры лямбда-выражений.
Изменяемая спецификация
Спецификация исключений
Дополнительные сведения см. в разделе спецификации исключений (throw).
Возвращаемый тип
Лямбда-выражение может создавать другое лямбда-выражение в качестве своего возвращаемого значения. Дополнительные сведения см. в разделе «лямбда-выражения более высокого порядка» в примерах лямбда-выражений.
Тело лямбды
Тело лямбда-выражения является составным оператором. Он может содержать все, что разрешено в теле обычной функции или функции-члена. Тело обычной функции и лямбда-выражения может осуществлять доступ к следующим типам переменных:
Фиксированные переменные из внешней области видимости (см. выше).
Локально объявленные переменные.
Члены данных класса, объявленные внутри класса и this захваченные.
Любая переменная, имеющая статическую длительность хранения, например глобальные переменные.
В следующем примере содержится лямбда-выражение, которое явно фиксирует переменную n по значению и неявно фиксирует переменную m по ссылке.
Дополнительные сведения см. в разделе Generate.
Дополнительные сведения см. в разделе generate_n.
constexpr лямбда-выражения
Visual Studio 2017 версии 15,3 и более поздних версий (доступно в режиме и более поздних версиях): лямбда-выражение можно объявить как constexpr (или использовать его в константном выражении), если инициализация каждого захваченного или введенного элемента данных разрешена в константном выражении.
Лямбда-выражение неявно, constexpr если его результат удовлетворяет требованиям constexpr функции:
Специально для систем Майкрософт
Visual Studio поддерживает стандартную лямбда-функцию c++ 11 и лямбда-выражения без отслеживания состояния. Лямбда без отслеживания состояния преобразуется в указатель функции, который использует произвольное соглашение о вызовах.
Лямбды: от C++11 до C++20. Часть 1
Добрый день, друзья. Сегодня мы подготовили для вас перевод первой части статьи «Лямбды: от C++11 до C++20». Публикация данного материала приурочена к запуску курса «Разработчик C++», который стартует уже завтра.
Лямбда-выражения являются одним из наиболее мощных дополнений в C++11 и продолжают развиваться с каждым новым стандартом языка. В этой статье мы пройдемся по их истории и посмотрим на эволюцию этой важной части современного C++.
На одном из местных собраний C++ User Group у нас был живой сеанс программирования по «истории» лямбда-выражений. Беседу вел эксперт по С++ Томас Каминский (Tomasz Kamiński) (см. профиль Томаса в Linkedin). Вот это событие:
Я решил взять код у Томаса (с его разрешения!), описать его и создать отдельную статью.
Мы начнем с изучения C++03 и с необходимости в компактных локальных функциональных выражениях. Затем мы перейдем к C++11 и C++14. Во второй части серии мы увидим изменения в C++17 и даже взглянем на то, что произойдет в C++ 20.
Но проблема заключалась в том, что вы должны были написать отдельную функцию или функтор в другой области видимости, а не в области видимости вызова алгоритма.
В качестве потенциального решения вы могли бы подумать о написании локального класса функторов — поскольку C++ всегда поддерживает этот синтаксис. Но это не работает…
Посмотрите на этот код:
По сути, в C++98/03 вы не можете создать экземпляр шаблона с локальным типом.
Из-за всех этих ограничений Комитет начал разрабатывать новую фичу, которую мы можем создавать и вызывать «на месте»… «лямбда-выражения»!
Если мы посмотрим на N3337 — окончательный вариант C++11, то увидим отдельный раздел для лямбд: [expr.prim.lambda].
Я думаю, что лямбды были добавлены в язык с умом. Они используют новый синтаксис, но затем компилятор «расширяет» его до реального класса. Таким образом, у нас есть все преимущества (а иногда и недостатки) реального строго типизированного языка.
Вот базовый пример кода, который также показывает соответствующий объект локального функтора:
Вы также можете проверить CppInsights, который показывает, как компилятор расширяет код:
Посмотрите на этот пример:
В этом примере компилятор преобразует:
Во что-то похожее на это (упрощенная форма):
Некоторые определения, прежде чем мы начнем:
Вычисление лямбда-выражения приводит к временному prvalue. Этот временный объект называется объектом-замыканием (closure object).
Тип лямбда-выражения (который также является типом объекта-замыкания) является уникальным безымянным non-union типом класса, который называется типом замыкания (closure type).
Несколько примеров лямбда-выражений:
Поскольку компилятор генерирует уникальное имя для каждой лямбды, узнать его заранее не представляется возможным.
Более того [expr.prim.lambda]:
Тип замыкания, связанный с лямбда-выражением, имеет удаленный ([dcl.fct.def.delete]) конструктор по умолчанию и удаленный оператор присваивания.
Поэтому вы не можете написать:
Это приведет к следующей ошибке в GCC:
Код, который вы помещаете в тело лямбды, «транслируется» в код operator() соответствующего типа замыкания.
По умолчанию это встроенный константный метод. Вы можете изменить его, указав mutable после объявления параметров:
Хотя константный метод не является «проблемой» для лямбды без пустого списка захвата… он имеет значение, когда вы хотите что-то захватить.
[] не только вводит лямбду, но также содержит список захваченных переменных. Это называется «список захвата».
Захватив переменную, вы создаете член-копию этой переменной в типе замыкания. Затем внутри тела лямбды вы можете получить к нему доступ.
Вы можете поиграться с полным примером здесь: @Wandbox
Хотя указание [=] или [&] может быть удобно — поскольку оно захватывает все переменные в автоматическом хранилище, более очевидно захватывать переменные явно. Таким образом, компилятор может предупредить вас о нежелательных эффектах (см., например, примечания о глобальных и статических переменных)
Вы также можете прочитать больше в пункте 31 «Эффективного современного C++» Скотта Мейерса: «Избегайте режимов захвата по умолчанию».
Замыкания С++ не увеличивают время жизни захваченных ссылок.
По умолчанию operator() типа замыкания является константным, и вы не можете изменять захваченные переменные внутри тела лямбда-выражения.
Если вы хотите изменить это поведение, вам нужно добавить ключевое слово mutable после списка параметров:
В приведенном выше примере мы можем изменить значения x и y… но это только копии x и y из прилагаемой области видимости.
Захват глобальных переменных
Если у вас есть глобальное значение, а затем вы используете [=] в лямбде, вы можете подумать, что глобальное значение также захвачено по значению… но это не так.
Поиграть с кодом можно здесь: @Wandbox
Захватываются только переменные в автоматическом хранилище. GCC может даже выдать следующее предупреждение:
Захват статических переменных
Захват статических переменных аналогичен захвату глобальных:
Поиграть с кодом можно здесь: @Wandbox
Захват члена класса
Знаете ли вы, что произойдет после выполнения следующего кода:
Поскольку мы используем временные объекты, мы не можем быть уверены, что произойдет, при вызове f1 и f2. Это проблема висячих ссылок, которая порождает неопределенное поведение.
Опять же, если вы укажете захват явно ([s]):
Компилятор предотвратит вашу ошибку:
Если у вас есть объект, который может быть только перемещен (например, unique_ptr), то вы не можете поместить его в лямбду в качестве захваченной переменной. Захват по значению не работает, поэтому вы можете захватывать только по ссылке… однако это не передаст его вам во владение, и, вероятно, это не то, что вы хотели.
Если вы захватываете константную переменную, то константность сохраняется:
В C++11 вы можете пропустить trailing возвращаемый тип лямбды, и тогда компилятор выведет его за вас.
Первоначально вывод возвращаемого типа значения был ограничен лямбдами, содержащими один оператор return, но это ограничение было быстро снято, поскольку не было проблем с реализацией более удобной версии.
Таким образом, начиная с C++11, компилятор может вывести тип возвращаемого значения, если все операторы return могут быть преобразованы в один и тот же тип.
Если все операторы return возвращают выражение и типы возвращаемых выражений после преобразования lvalue-to-rvalue (7.1 [conv.lval]), array-to-pointer (7.2 [conv.array]) и function-to-pointer (7.3 [conv.func]) такое же, как у общего типа;
Поиграться с кодом можно здесь: @Wandbox
IIFE — Немедленно вызываемые выражения (Immediately Invoked Function Expression)
В наших примерах я определял лямбду, а затем вызвал ее, используя объект замыкания… но ее также можно вызывать немедленно:
Такое выражение может быть полезно при сложной инициализации константных объектов.
Преобразование в указатель на функцию
Тип замыкания для лямбда-выражения без захвата имеет открытую невиртуальную неявную функцию преобразования константы в указатель на функцию, имеющую тот же параметр и возвращаемые типы, что и оператор вызова функции типа замыкания. Значение, возвращаемое этой функцией преобразования, должно быть адресом функции, которая при вызове имеет тот же эффект, что и вызов оператора функции типа сходного с типом замыкания.
Другими словами, вы можете преобразовывать лямбды без захватов в указатель на функцию.
Поиграться с кодом можно здесь: @Wandbox
C++14 добавил два значительных улучшения в лямбда-выражения:
Вывод типа возвращаемого значения лямбда-выражения был обновлен, чтобы соответствовать правилам автоматического вывода для функций.
Возвращаемый тип лямбды — auto, который заменяется trailing возвращаемым типом, если он предоставляется и/или выводится из операторов возврата, как описано в [dcl.spec.auto].
Захваты с инициализатором
Короче говоря, мы можем создать новую переменную-член типа замыкания и затем использовать ее внутри лямбда-выражения.
Это может решить несколько проблем, например, с типами, доступными только для перемещения.
Теперь мы можем переместить объект в член типа замыкания:
Другая идея состоит в том, чтобы использовать его как потенциальную технику оптимизации. Вместо того, чтобы вычислять какое-то значение каждый раз, когда мы вызываем лямбду, мы можем вычислить его один раз в инициализаторе:
Инициализатор также можно использовать для захвата переменной-члена. Затем мы можем получить копию переменной-члена и не беспокоиться о висячих ссылках.
Поиграться с кодом можно здесь: @Wandbox
В foo() мы захватываем переменную-член, копируя ее в тип замыкания. Кроме того, мы используем auto для вывода всего метода (ранее, в C++11 мы могли использовать std::function ).
Еще одно существенное улучшение — это обобщенная лямбда.
Начиная с C++14 можно написать:
Это эквивалентно использованию объявления шаблона в операторе вызова типа замыкания:
Такая обобщенная лямбда может быть очень полезна, когда трудно вывести тип.
Вы можете поиграться кодом здесь: @Wandbox
В этой статье мы начали с первых дней лямбда-выражений в C++03 и C++11 и перешли к улучшенной версии в C++14.
Вы увидели, как создавать лямбду, какова основная структура этого выражения, что такое список захвата и многое другое.
В следующей части статьи мы перейдем к C++17 и познакомимся с будущими фичами C++20.
Вторая часть доступна здесь:
Ждем ваши комментарии и приглашаем всех заинтересованных на курс «Разработчик С++».
C++0x (С++11). Лямбда-выражения
Буквально на днях случайно наткнулся на Хабре на статью о лямбда-выражениях из нового (будущего) стандарта C++. Статья хорошая и даёт понять преимущества лямбда-выражений, однако, мне показалось, что статья недостаточно полная, поэтому я решил попробовать более детально изложить материал.
Вспомним основы
Лямбда-выражения — одна из фич функциональных языков, которую в последнее время начали добавлять также в императивные языки типа C#, C++ etc. Лямбда-выражениями называются безымянные локальные функции, которые можно создавать прямо внутри какого-либо выражения.
В прошлой статье лямбда-выражения сравнивали с указателями на функции и с функторами. Так вот первое, что следует уяснить: лямбда-выражения в C++ — это краткая форма записи анонимных функторов. Рассмотрим пример:
Фактически данный код целиком соответствует такому:
Вывод соответственно будет следующим:
На что здесь стоит обратить внимание. Во-первых, из Листинга 1 мы видим, что лямбда-выражение всегда начинается с [] (скобки могут быть непустыми — об этом позже), затем идет необязательный список параметров, а затем непосредственно тело функции. Во-вторых, тип возвращаемого значения мы не указывали, и по умолчанию лямбда возвращает void (далее мы увидим, как и зачем можно указать возвращаемый тип явно). В-третьих, как видно по Листингу 2, по умолчанию генерируется константный метод (к этому тоже еще вернемся).
Не знаю, как вам, но мне for_each, записанный с помощью лямбда-выражения, нравится гораздо больше. Попробуем написать немного усложненный пример:
В данном случае лямбда играет роль унарного предиката, то есть тип возвращаемого значения bool, хотя мы нигде этого не указывали. При наличии одного return в лямбда-выражении, компилятор вычисляет тип возвращаемого значения самостоятельно. Если же в лямбда-выражении присутствует if или switch (или другие сложные конструкции), как в приведенном ниже коде, то на компилятор полагаться уже нельзя:
Код из Листинга 4 не компилируется, а, к примеру, Visual Studio пишет ошибку на каждый return такого содержания:
Компилятор не может самостоятельно вычислить тип возвращаемого значения, поэтому мы должны его указать явно:
Теперь компиляция проходит успешно, а вывод, как и ожидалось, будет следующим:
Единственное, что мы добавили в Листинге 5, это тип возвращаемого значения для лямбда-выражения в виде -> double. Синтаксис немного странноват и смахивает больше на Haskell, чем на C++. Но указывать возвращаемый тип «слева» (как в функциях) не получилось бы, потому что лямбда должна начинаться с [], чтобы компилятор смог её различить.
Захват переменных из внешнего контекста
Все лямбда-выражения, приведенные выше, выглядели как анонимные функции, потому что не хранили никакого промежуточного состояния. Но лямбда-выражения в C++ — это анонимные функторы, а значит состояние они хранить могут! Используя лямбда-выражения, напишем программу, которая выводит количество чисел, попадающих в заданный пользователем интервал [lower; upper):
Наконец, мы добрались до того момента, когда лямбда-выражение начинается не с пустых скобок. Как видно в Листинге 6, внутри квадратных скобок могут указываться переменные. Это называется… эээм… «список захвата» (capture list). Для чего это нужно? На первый взгляд может показаться, что внешней областью видимости для лямбда-выражения является функция main() и мы можем беспрепятственно использовать переменные, объявленные в ней, внутри тела лямбда-выражения, однако это не так. Почему? Потому что фактически тело лямбды — это тело перегруженного operator()() (как бы это назвать… оператора функционального вызова что ли) внутри анонимного функтора, то есть для кода из Листинга 6 компилятор неявно сгенерирует примерно такой код:
Листинг 7 немного всё разъясняет. Наша лямбда превратилась в функтор, внутри тела которого мы не можем напрямую использовать переменные, объявленные в main(), так как это непересекающиеся области видимости. Для того чтобы доступ к lowerBound и upperBound все-таки был, эти переменные сохраняются внутри самого функтора (происходит тот самый «захват»): конструктор их инициализирует, а внутри operator()() они используются. Я специально дал этим переменным имена, начинающиеся с префикса «m_», чтобы подчеркнуть различие.
Если мы попытаемся изменить «захваченные» переменные внутри лямбды, нас ждет неудача, потому что по умолчанию генерируемый operator()() объявлен как const. Для того чтобы это обойти, мы можем указать спецификатор mutable, как в следующем примере:
Ранее я упоминал, что список параметров лямбды можно опускать, когда он пустой, однако для того чтобы компилятор правильно распарсил применение слова mutable, мы должны явно указать пустой список параметров.
При выполнении программы из Листинга 8 получаем следующее:
Как видим, благодаря ключевому слову mutable, мы можем менять значение «захваченной» переменной внутри тела лямбда-выражения, но, как и следовало ожидать, эти изменения не отражаются на локальной переменной, так как захват происходит по значению. C++ позволяет нам захватывать переменные по ссылке и даже указывать «режим захвата», используемый по умолчанию. Что это означает? Мы можем не указывать каждую переменную в списке захвата по отдельности: вместо этого можно просто указать режим по умолчанию для захвата, и тогда все переменные из внешнего контекста, которые используются внутри лямбды, будут захвачены компилятором автоматически. Для указания режима захвата по умолчанию существует специальный синтаксис: [=] или [&] для захвата по значению и по ссылке соответственно. При этом для каждой переменной можно указать свой режим захвата, однако режим по умолчанию, естественно, указывается только единожды, причем в самом начале списка захвата. Вот варианты использования:
Следует отметить, что синтаксис наподобие &out в данном случае не означает взятие адреса. Его следует читать скорее как SomeType & out, то есть это просто передача параметра по ссылке. Рассмотрим пример:
В этот раз вместо явного захвата переменной init, я указал режим захвата по умолчанию: [&]. Теперь когда компилятор встречает внутри тела лямбды переменную из внешнего контекста, он автоматически захватывает её по ссылке. Вот эквивалентный Листингу 9 код:
И соответственно вывод будет следующим:
Теперь вам главное не запутаться, что, где и когда передавать по ссылке. Фактически, если мы указываем [&] и не указываем mutable, то все равно сможем менять значение захваченной переменной и это отразится на локальной, потому что operator()() const подразумевает, что мы не можем менять, на что указывает ссылка, а это и так невозможно.
Я так понял, что выполнить захват «по константной ссылке» невозможно. Ну, может, оно нам и не надо.
А что будет, если мы напишем такое, слегка надуманное лямбда-выражение внутри метода класса:
Несмотря на все наши ожидания, код не будет скомпилирован, так как компилятор не сможет захватить m_val и m_power: эти переменные вне области видимости. Вот что говорит на это Visual Studio:
Как же быть? Чтобы получить доступ к членам класса, в capture-list нужно поместить this:
Данная программа делает именно то, чего мы ожидали:
1 2 4 8 16 32 64 128 256 512 1024
Следует заметить, что this можно захватить только по значению, и если вы попытаетесь произвести захват по ссылке, компилятор выдаст ошибку. Даже если вы в коде из Листинга 12 напишете [&] вместо [this], то this будет все равно захвачен по значению.
Прочее
Помимо всего вышеперечисленного, в заголовке лямбда-выражения можно указать throw-list — список исключений, которые лямбда может сгенерировать. Например, такая лямбда не может генерировать исключения:
А такая генерирует только bad_alloc:
Естественно, если его не указывать, то лямбда может генерировать любое исключение.
К счастью, в финальном варианте стандарта throw-спецификации объявлены устаревшими. Вместо этого оставили ключевое слово noexcept, которое говорит, что функция не должна генерировать исключение вообще.
Таким образом, общий вид лямбда-выражения следующий (сорри за такой «вольный вид» грамматики):
Повторное использование лямбда-выражений. Генерация лямбда-выражений.
Все вышеперечисленное довольно удобно, но основная мощь лямбда-выражений приходится на то, что мы можем сохранить лямбду в переменной или передавать как параметр в функцию. В Boost для этого есть класс Function, который, если я не ошибаюсь, войдет в новый стандарт STL (возможно, в немного измененном виде). На данный момент уже можно поюзать фичи из обновленного STL, однако, пока что эти фичи находятся в подпространстве имен std::tr1.
Возможность сохранения лямбда-выражений позволяет нам не только повторно использовать лямбды, но и писать функции, которые генерируют лямбда-выражения, и даже лямбды, которые генерируют лямбды.
Рассмотрим следующий пример:
Данная программа выводит:
0 1 2 3 4 5 6 7 8 9
2 3 4 5 6 7 8 9 10 11
Рассмотрим подробнее. Вначале у нас инициализируется вектор с помощью generate_n(). Тут всё просто. Далее мы создаем переменную traceLambda типа function (то есть функция, принимающая int и возвращающая void) и присваиваем ей лямбда-выражение, которое выводит на консоль значение и пробел. Далее мы используем только что сохраненную лямбду для вывода всех элементов вектора.
После этого мы видим немаленькое объявление lambdaGen, которая является лямбда-выражением, принимающим один параметр int и возвращающим другую лямбду, принимающую int и возвращающую int.
Следом за этим мы ко всем элементам вектора применяем transform(), в качестве мутационной функции для которого указываем lambdaGen(2). Фактически lambdaGen(2) возвращает другую лямбду, которая прибавляет к переданному параметру число 2 и возвращает результат. Этот код, естественно, немного надуманный, ибо то же самое можно было записать как
однако в качестве примера довольно показательно.
Затем мы снова выводим значения всех элементов вектора, используя для этого сохраненную ранее лямбду traceLambda.
На самом деле, данный код можно было записать еще короче. В новом стандарте C++ значение ключевого слова auto будет заменено. Если раньше auto означало, что переменная создается в стеке, и подразумевалось неявно в случае, если вы не указали что-либо другое (register, к примеру), то сейчас это такой себе аналог var в C# (то есть тип переменной, объявленной как auto, определяется компилятором самостоятельно на основе того, чем эта переменная инициализируется).
Следует заметить, что auto-переменная не сможет хранить значения разных типов в течение одного запуска программы. C++ как был, так и остается статически типизированным языком, и указание auto лишь говорит компилятору самостоятельно позаботиться об определении типа: после инициализации сменить тип переменной будет уже нельзя.
Кроме того что ключевое слово auto весьма полезно при работе с циклами вида
его очень удобно использовать с лямбда-выражениями. Теперь код из Листинга 13 можно переписать так:
Пожалуй, на этом я закончу описание лямбда-выражений. Если будут вопросы, поправки или замечания, с удовольствием выслушаю.
ETA (20.02.2012): Оказалось, что для кого-то эта статья до сих пор актуальна, поэтому поправил подсветку синтаксиса и подкорректировал информацию про throw-списки в объявлении лямбд. Помимо непосредственно лямбда-выражений другие фичи из нового стандарта С++11 (например, списки инициализации контейнеров) решил не добавлять, так что статья осталась практически в первозданном виде.