
Многие языки программирования позволяют создавать налету локальные не именованные функции внутри выражений. К этим языкам относятся C#3.0, Python, ECMAscript и практически все функциональные языки программирования (например, Haskell и Scheme). Такие функции обычно относят к лямбда функциям, которые имеют широкое применение.
C++0x– неофициальное название нового стандарта языка C++, который, как планируется, должен заменить существующий стандарт(ISO/IEC 14882), который был опубликован в 1998 и обновлен в 2003. Предшественники также известны, как C++98 и C++03. Новый стандарт добавит несколько дополнений в язык и расширит стандартную библиотеку C++. Одно из расширений языка – возможность использования лямбда выражений. Очень приятно, что уже сегодня данная возможность реализована для VisualStudio 2010 и GCC.

Лямбда выражения – техника программирования, сочетающая в себе преимущества указателей на функции и функциональных объектов, позволяет избежать неудобств. Как и функциональные объекты, лямбда выражения позволяют хранить состояния, но их компактный синтаксис в отличие от функциональных объектов не требует объявления класса. Лямбда выражения позволяют вам писать более компактный код и избежать ошибок, нежели используя функциональные объекты.
Напомню,
функциональные объекты – это обыкновенные объекты с перегруженным“()”оператором. Таким образом, с точки зрения синтаксиса, они являются обыкновенными функциями.
Для начала давайте рассмотрим пример с использованием указателя на функцию. Представим, что нам нужно заполнить массив числами от 0 до 19, один из вариантов заполнения массива, используя текущий стандарт, приведен ниже.
Минус данного решения в том, что мы не можем отслеживать состояние нашей переменной “value” вне функции.
Теперь реализуем туже самую задачу с помощью функционального объекта. Вариант реализации приведен ниже.

Это техника прекрасно работает и позволяет отслеживать состояние переменной “n”, но она требует объявления класса.
А теперь давайте посмотрим, как просто можно выполнить ту же самую операцию, используя лямбда выражение.
Синтаксис лямбда выражения:
.png)
1) Маска переменных
2) Список параметров
3) Изменение параметра, переданного по значению
4) Спецификация исключения
5) Возвращаемый тип
6) Тело лямбда выражения
Маска переменных
Лямбда выражение может получать доступ практически к любым переменным и использовать их внутри тела. Снимок определяет способ получения параметров телом лямбда выражения. Доступ к переменным, перед которыми стоит амперсанд (&), осуществляется по ссылке, а перед которыми нет амперсанда, соответственно по значению. Пустая маска ([]) означает, что тело выражения не имеет доступа к переменным.
Стандартная маска определяет, получаем ли мы доступ к переменной по ссылке или же по значению. Устанавливая “&”, все значения будут получены по ссылке. Используя же“=”, переменные будут получены по значению. Например, если тело лямбда выражения получает доступ к внешней переменной “total” по ссылке и переменной “factor” по значению, маска должна быть записана следующим образом:

Также, вы можете использовать лямбда выражение внутри метода класса. Передавая указатель “this”, вы можете получить доступ к методам и членам данного класса.
Список параметров
Список параметров лямбда выражения содержит список параметров для функции, у которых существуют следующим ограничения:
1) Список параметров не может содержать значения по умолчанию
2) Не может содержать неименованные параметры
3) Ограниченное число параметров

Также лямбда выражение может принимать в качестве параметра другое лямбда выражение. С другой стороны, список параметров является опциональным элементов, и можно писать выражение, не включая его.
Изменение параметра переданного по значению
Спецификация позволяет разрешить телу лямбда выражения и заменить переменные, полученные по значению.
Спецификация исключений
Лямбда выражения может включать в себя спецификацию исключения, которая позволяет телу не создавать исключения.
Возвращаемый тип
Спецификация определяет возвращаемый тип лямбда выражения. Вы можете не использовать данную конструкцию, если тело выражения имеет только одну инструкцию “return” или не возвращает значения вообще. Если тело содержит только одну “return” инструкцию, компилятор установит возвращаемый тип лямбда выражения идентичный типу “return” инструкции.
Тело лямбда выражения
На тело выражения накладываются идентичные ограничения, как на блок кода или метод.
Данная фича уже доступна в VisualStudio 2010 и GCC, думаю, она вам поможет сделать ваш код еще лучше.
комментарии (141)
У меня в MSVC такой же шрифт.
Это как? Только не говорите про анонимные классы. Класс — не функция.
Из-за угла — да, страшно.
Ну и представьте каково разбирать подобное адептам визуального бейсика.
А вообще эта погоня за обратной совместимостью и бесконечные костыли делают с++ просто нечитаемым.
ЗЫ: Перл я тоже люблю :)
:)))
ps. Запутанность С++ только могила исправит. Простите :)
Of course not. Read the real IEEE interview.
С другой — если кому-то нужно ФП то добро пожаловать в Lisp, Haskell или Erlang. Когда-то Си был таким удобным, приятным языком)
А вот из Python Гвидо ФП весь выпилил.
И GCC — самый наверное распространенный компилятор, над которым работала уйма людей, в том числе — и пытаясь ввернуть туда хвостовую рекурсию. Может меня обрадуют и ее все-таки уже сделали?
В итоге к хорошему императивному языку рискуют приделать не слишком пригодные фрагменты ФП. Кому это надо? Как я писал уже выше, из Питона последовательно убирают ФП-шность. Попробовали — не сочетается.
Да помнится, Гвидо писал в своем блоге, что в гробу он видал лямбду и пользователи все равно ее не используют а кто использует — может обойтись именованными функциями, так что в будущем это уберут.
www.python.org/dev/peps/pep-3099/
Я подозреваю, что сделали потому, что это не так уж и тяжело сделать, да и не грех пополнить список оптимизаций.
Ну кагбе каким он был таким он и остался
Освященный
векамидесятилетиями ФП подход — лексическое замыкание. См. яваскрипт — мало кто знает, что с определенной точки зрения это такой маленький, хороший ФП язык)Но как впихнуть лексические замыкания в C/C++? Какой смайлик для «жуткий скеписис»?
++ из-за того, что в Си++ впервые появился такой оператор :), ну и потом ++ это инкремент, т.е. следующая версия. В Си++ появились классы, операторы new и delete. Насчет шаблонов не уверен… кажись они попозднее были.
«С» — как дань предшественнику и основе языка;
"++" — как указание на усовершенствование.
А оператор ++ был и в Си.
int fact = [](int x) -> int
{
int accumulator = 1;
beginning:
if (x == 0) return accumulator;
else
{
accumulator *= x;
x -= 1;
goto beginning;
}
}(n);
… }(n) меня слегка ушибло
То есть просто вызов этой лямбды
Во-вторых, тем, кто использовал Boost.Lambda, станет легче, и это хорошо. А для прочих ничего не изменится :)
А язык и так громадный с множеством тонкостей. Лучше уж библиотека чем такие рывки.
std::tr1::function<void (int)> print = [](int n) { std::cout << n; };
[](){}();
насчет орущий +пятьсот, каждый хитрый выверт таких порождает, потому как въезжать в это нужно. Часто материал подается в руководствах по новым фичам через жопу, потому как авторы манула еще сами не поняли на кой оно им. Последние курсы майкрософта вобще жесть сплошная.
Откуда берутся переменные не объявленные в скобках? Из текущего контекста, из контекста применения выражения?
> Также лямбда выражение может принимать в качестве параметра другое лямбда выражение. С другой стороны, список параметров является опциональным элементов, и можно писать выражение, не включая его.
Неплохо бы уточнить, что результат скорее всего будет отличаться. Выражения [=]{return x;} и [=](int x){return x;} вообще-то разные вещи (надеюсь :))
Да, будет отличаться! В первом случае переменная «x» будет взята из контекста, второй вариант вернет входной параметр
А код «о, смотри, какую я штуку написал! угадай, что оно делает?» — плохой код, потому что он отнимает уйму человекочасов на поддержке.
Не сильно понимаю, как конструкция из множества скобок и спецсимволов (которую большинство постарается ещё и в одну строку записать) поможет «сделать код лучше»? Примером «как не стоит писать»?
PS: функция inc() случаем не ноль всегда возвращать будет? Кажется, там логическая ошибочка с местом инициализации переменной value ;)
почитайте на досуге, что означает модификатор static
// test.cpp, code begin
#include using namespace std;
int inc() {
static int z = 0;
return z++;
}
int main(void) {
for (int i = 0; i < 5; i++) {
cout << int() << endl;
}
return 0;
}
// code end
~$ g++ test.cpp && ./a.out
0
0
0
0
0
~$
Думайте, прежде чем минусовать
В какой-то книге встречал фразу чувака, который давно программит на нём и уже может считаться гуру (не дословно, но смысл передам): «Я программлю на C++ уже N лет, но даже с моим опытом я делаю по крайней мере одну глупость каждый день»
А что за версия C++ с «волшебными статиками»?
1. для переменных внутри функции (как раз то, что в примере) он означает, что инициализация переменной осуществляется только при первом вхождении в функцию, а для всех последующих вхождений используется значение полученное на предыдущем вхождении
2. для переменных в классах это означает, что эта переменная одна для всех объектов и наследников и меняя значение этой переменной для одного объекта оно меняется для всех объектов этого (или наследованного) типа. Применение: подсчёт указателей на память, например.
3. переменных и функций, объявленных глобально с cpp-файле это означает, что область видимости этой переменной ограничена только этим файлом.
4. для методов, которые static это превращает их практически в аналог глобальных функций для вызова которых не нужно создавать объект этого класса. Отличие от глобальных функций только вроде только в том, что на статические функции работает правило области видимости, т.е. приватную или протектную ты объявить где угодно не сможешь.
Ну вот то немногое, что просто «на коленке» вспомнил особо не задумываясь. Потому " static is a miracle !" :)
* Инициализировать static-поле при декларации (случай 2 в Вашем списке) разве нельзя? Если можно — то он идентичен случаю 1
* В случае 3 — слово static делает объявленя приватными? Замысловато, однако. А почему в стандарте для этого не предлагается слово типа private?
* 4-ый случай — это как в Delphi объявление методов класса с помощью слова class? Интересно, почему тут оно объявляется словом static? Или они имеют ввиду, что метод — это типа ссылка на функцию, делают эту ссылку независимой от экземпляра, «статичной»?
* В фразе «приватную или протектную ты объявить где угодно не сможешь» — тут Вы имели ввиду «вызвать» а не «объявить», или я что-то не понял?
В случае 3 static функции и переменные не являются членами какого-то класса, static ограничивает область доступности этих переменных и функций текущей единицей трансляции.
Потому что эти методы могут вызываться без создания экземпляра класса (к статическим членам-данным тоже можно обращаться без создания экземпляра), кроме того, они могут доступаться к любым статическим (в том числе и приватным) членам-данным класса.
Непонятная формулировка. Что значит к практически любым? Может к переменным из текущей области видимости?
gcc.gnu.org/gcc-4.5/cxx0x_status.html
вот смотрим статус еще не вышедшей но разрабатываемой 4.5 :-( :-(
std::for_each
std::accumulate
std::copy
std::transform
очень много выиграют от появления лямбда-функций. Конечно если не знать про существование таких удобных вещей, можно писать в си-стиле
for( i = 0; i<container.size(); ++i){
//somecode
};
for( iter=container.begin();iter!=container.end(); ++iter){
//some code on *iter
};
потом вспоминать, что нехорошо container.end() вызывать каждый раз, на списке из сотен тысяч элементов, и добавлять
end_iter=container.end()
потом вспоминать, что *iter тоже функция и лучше бы ее тоже отдельно взять:
elem &e= *iter;
не проще ли сразу:
std::for_each( container.begin(), container.end(), [&](){//some code here} );
? :)
И да я рад возможности писать for_each с лямбдами, но опять же есть достаточно простые пути без него обойтись (даже на странном компиляторе =).
end()
{ return iterator(this->_M_impl._M_finish); }
то есть как минимум каждый раз создание нового итератора, вызов его конструктора и деструктора по выходу из блоков.
а если посмотреть в std::string::end() или еще какой-нибудь сложный контейнер?
Насчет полезности: кроме очевидных примеров с stl функциями которые требуют функтор можно найти еще кучу применений. Например кода есть куски кода которые очень похожи, но если их вынести в отдельную функцию то надо передавать 28 переменных. В таком случае можно объявить лямбда функцию и вызывать ее прямо на месте.
Те же qt сигнали реализованные на tr1::function + лямбда функции были бы гораздо удобнее.
Вобщем по-моему замечательное дополнение к языку
лямбда функция живет в блоке кода :) ей доступны переменные «на стеке» из этого блока, по выходу из блока все прекрасно «со стека» уберется :)
лямбда функция переданная как параметр в другой блок — не имеет доступа к переменным текущего блока :) поэтому никаких проблем без сборщика мусора нет.
Вообще в старом с++ всё тоже самое можно было бы сделать «ручками» написав чтото типа
class MyLambda{
sometype &someval;
sometype &someval;
…
public:
MyLambda( &some, &some… );
void operator()( params )
};
но каждый раз это делать несколько задалбывает
int* ptr = new int(0);
return [=](){return (*ptr)++;};
}
по мне так все нормально с доступом к перемнной на стеке в текущем блоке и передавать ее надо по значению. В другом месте надо по ссылке передать. Так что не все понятно.
я еще мощнее вам могу пример рассказать из с++
int *f(){
int val = 0;
return &val;
};
тут уже выше неоднократно высказывалось, что если инструмент позволяет гвоздь и забить, и закрутить и закатать внутрь стены, а мастер знает только то что инструмент для закатывания, то мастер сам себе дурак :)
int f(){
int *ptr = new int(0);
return [=](){ return (*ptr)++ };
};
да вы тут потеряете память, но причем тут лямбда функции?
точно также можно написать без лямбды:
int f(){
int *ptr = new int(0);
return (*ptr)++;
};
tr1::function<int(void)> f() {....}
Если вас волнует потеря памяти (я понимаю при работе с c++ это уже инстинкт =) то можно написать что-то вроде.
typedef tr1::function<int(void)> F;
pair<F, F> f() {
shared_ptrptr(new int(0));
return make_pair([=ptr](){ return (*ptr)++;}, [=ptr](){ return (*ptr)--;});
}
Понятно что такое решение ни полраза не потокобезопасно. Но с другой стороны мы получили пару функций связанных одной переменной без необходимости хранить эту переменную где-то еще.
У нас наблюдается терменологическая путаница. Есть лямбда функции. Они в данном контексте не более чем функции без имени. Так сказать «syntactic sugar». Но часто вместе с лямбда функциями используются замыкания. Замыкания как я представляю нужны для того чтобы сохранить часть переменных для последующего использования внутри лямбда функции.
Своим неудачным примером я хотел показать что вопрос о том как именно сохранять эти переменные не всегда очевидно для компилятора. Например понятно что можно сохранить перемнные со стека по значению. Но если программист знает что эти переменные очень тяжелые и что функция не будет использована за пределами данного блока видимости, то лучше хранить их по ссылке.
например
void f(vector* v) {
hash_setbig_hash_set;
// fill big_hash_set
stable_partition(v->begin(), v->end(), [&big_hash_set](int i) { return big_hash_set.count(i); }
…
}
Если бы мне пришлось разрабатывать замыкания в c++, то я бы, скорее всего, остановился на том, чтобы все хранить по значению. Те кого это не устраивает могли бы передавать все через указатели (умные и не очень). В таком случае мой пример выглядел бы как
void f(vector* v) {
hash_setbig_hash_set;
// fill big_hash_set
hash_set* big_hash_set_ptr = &big_hash_set;
stable_partition(v->begin(), v->end(), (int i) { return big_hash_set_ptr->count(i); }
…
}
С другой стороны та реализация которую предлагают в стандарт мне тоже нравится.
замыканий как таковых (как я понимаю из текущего стандарта) лямбда-функции не реализуют, так как все переменные объявленные внутри лямбда функции будут при каждом вызове переобъявляться и по выходу из лямбды удаляться из стека.
Может я не прав, но тогда не понятна зачем эта бодяга с хранением по значению или ссылке.
если по ссылке, то отражается.
Насчет замыканий. Было бы странно ожидать работоспособности такого кода:
boost::function<void()> func;
void f(){
int a = 3;
func = [&](){ a++; };
};
f();
func();
время жизни переменной a, по стандарту языка, кончилось по выходу из f(). Соответственно непонятно к какой перменной будет иметь доступ лямбда.
P.S. что собственно и описано в пропозале:
9… If one or more names in the effective capture set are preceded by &, the effect of invoking a closure object, or a copy, after the innermost block scope of the context of the lambda expression has been exited is undefined.