Evgenii Legotckoi
Evgenii Legotckoi13 февраля 2018 г. 18:13

Готовим лямбда функции в C++ - Часть 1

Мой любимый инструмент в C++ - это лямбда функции, хотя как-то мне говорили, что они кажутся страшными. На самом деле они прелестны. Они значительно упрощают написание программ и позволяют делать довольно интересные решения.

Но прежде чем рассматривать различные способы применения лямбда функций, предлагаю ознакомиться с основным синтаксисом лямбда функций.

Возможные варианты синтаксиса лямбда функций

[ capture ] ( params ) mutable exception attribute -> ret { body }
[ capture ] ( params ) -> ret { body }
[ capture ] ( params ) { body }
[ capture ] { body }

Первый вариант является полным, но не запрещается использовать сокращённые вариации записи функций.

  • capture - список  внешних захватываемых объектов, они могут захватываться как по ссылке, так и копированием.
  • params - список параметров, передаваемых в лямбда функции, данная часть будет аналогична записи аргументов для обычных функций.
  • mutable - использование mutable позволяет модифицировать копии объектов, которые были захвачены копированием. В обычном варианте они не будут модифицироваться.
  • exception - обеспечивает спецификацию исключения, то есть лямбда функции также как и обычные функции могут выкидывать исключения.
  • attribute - обеспечивает спецификацию атрибута, таких атрибутов в спецификации C++ определено всего два ([[noreturn]], [[carries_dependency]])
  • params - список параметров, передаваемых в лямбда функцию
  • ret - возвращаемое значение лямбда функции

Что касается возвращаемого значение, то оно может автоматически выводиться из типа объекта, который возвращается оператором return. Если же в лямбда-функции отсутствует оператор return, то возвращаемое значение будет void.

Лямбда функция создаёт безымянный временный объект уникального безымянного non-union, non-aggregate типа, известного как тип замыкания. Благодаря введению оператора auto в современном стандарте C++ можно объявить объект лямбда функции довольно легко, без прописывания объявления функтора ( std::function ) со всеми апраметрами и возвращаемыми значениями, что делает код более простым и читаемым (для опытного программиста, конечно. Безусловно нужно учитывать то, что новичок быстрее заподозрит неладное, если в объявлении лямбды будет фигурировать std::function, но это уже вопрос практики).

Вот пример объявления простой лямбда функции, которая будет возвращать тип void , поскольку отсутствует хотя бы один оператор return .

#include <iostream>

using namespace std;

int main()
{
    auto myLambda = []()
    {
        cout << "Hello World!" << endl;
    };

    myLambda();

    return 0;
}

Соответственно программный код не скомпилируется, если в лямда-функции будет присутствовать два и более оператора return, которые будут возвращать объекты разных типов, не связанных между собой иерархией наследования и не способные быть приведены к типу базового класса. И даже, если эти объекты имеют базовый класс, необходимо будет прописать тип возвращаемого значения, им как раз будет указатель на объект базового класса (в общем случае).

Вот пример кода, который не скомпилируется.

#include <iostream>

using namespace std;

class A
{
public:
    A(){}
};

class B : public A
{
public:
    B(){}
};

class C : public A
{
public:
    C(){}
};

int main()
{
    auto myLambda = [](int type)
    {
        if (type == 0)
        {
            return new B();
        }
        else
        {
            return new C();
        }
    };

    myLambda(0);

    return 0;
}

Нужно указать тип возвращаемого значения

auto myLambda = [](int type) -> A* // Укажем тип возвращаемого значения
{
    if (type == 0)
    {
        return new B();
    }
    else
    {
        return new C();
    }
};

Также ошибка компиляции будет в том случае, если не указать тип возвращаемого значения и при этом вы создаёте в куче объект внутри лямбда функции, но в некоторых случаях можете вернуть указатель на nullptr. То есть ниже следующий код не скомпилируется.

#include <iostream>

using namespace std;

class A
{
public:
    A(){}
};


int main()
{
auto myLambda = [](int type)
{
    if (type == 0)
    {
        return new A();
    }
    else
    {
        return nullptr;
    }
};

    myLambda(0);

    return 0;
}

Опять нужно указать тип возвращаемого значения

auto myLambda = [](int type) -> A* // Укажем тип возвращаемого значения
{
    if (type == 0)
    {
        return new A();
    }
    else
    {
        return nullptr;
    }
};

Дело в том, что nullptr - это универсальный тип данных, который в каком-то смысле не является типом данных, поскольку его нельзя установить в качестве типа переменной. Но он может быть присвоен в качестве значения указателю на объект. Чтобы неявное преобразование в данном случае происходило правильно, нужно также указать тип возвращаемого значения.

Также в выше приведённом примере показано, как вызвать лямда функцию и передать в неё параметры. Заметили? В данном примере используется параметр int type , в зависимости от которого мы возвращаем указатель на созданный объект или nullptr .

Также в лямбда функциях присутствует понятие захвата переменных. Это означает, что лямбда функция может использовать не только переменные, которые передаются ей в качестве параметров, но и какие-либо объекты, которые были объявлены вне лямда-функции.

Список символов может быть передан следующим образом:

  • **[a,&b]**
    где a захвачена по значению, а b захвачена по ссылке.
  • **[this]**
    захватывает указатель
    **this**
    по значению.
  • **[&]**
    захват всех символов по ссылке
  • **[=]**
    захват всех символов по значению
  • **[]**
    ничего не захватывает

Про захват переменных поговорим в следующих статьях.

Но отмечу один интересный момент, лямда-функцию можно вызвать сразу же там, где вы её и объявили, если добавить после тела лямда функции круглые скобки и передать все необходимые параметры, если они имеются.

Например такой код тоже скомпилируется

#include <iostream>

using namespace std;

class A
{
public:
    A(){}
};


int main()
{

    A* myA = [](int type) -> A*
    {
        if (type == 0)
        {
            return new A();
        }
        else
        {
            return nullptr;
        }
    }(0);

    return 0;
}

Так что подумайте, скомпилируется ли следующий программный код?

int main(){[](){}();}
Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
Дмитрий

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:60баллов,
  • Очки рейтинга-1
Дмитрий

C++ - Тест 003. Условия и циклы

  • Результат:92баллов,
  • Очки рейтинга8
d
  • dsfs
  • 26 апреля 2024 г. 12:56

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80баллов,
  • Очки рейтинга4
Последние комментарии
k
kmssr9 февраля 2024 г. 3:43
Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко5 февраля 2024 г. 10:50
Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 декабря 2023 г. 19:30
Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 декабря 2023 г. 17:38
Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik19 декабря 2023 г. 6:01
Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Сейчас обсуждают на форуме
G
George137 мая 2024 г. 8:27
добавить qlineseries в функции в функции: "GPlotter::addSeries(QString title, QVector &arr)" я вызываю метод setChart(...), я в конструктор передал адрес на QChartView элемент
BlinCT
BlinCT5 мая 2024 г. 13:46
Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
PS
Peter Son4 мая 2024 г. 1:57
Best Indian Food Restaurant In Cincinnati OH Ready to embark on a gastronomic journey like no other? Join us at App india restaurant and discover why we're renowned as the Best Indian Food Restaurant In Cincinnati OH . Whether y…
Evgenii Legotckoi
Evgenii Legotckoi2 мая 2024 г. 22:07
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.
IscanderChe
IscanderChe30 апреля 2024 г. 12:22
Во Flask рендер шаблона не передаётся в браузер Доброе утро! Имеется вот такой шаблон: <!doctype html><html> <head> <title>{{ title }}</title> <link rel="stylesheet" href="{{ url_…

Следите за нами в социальных сетях