Все о лямбда-функциях в C ++

  • 2 декабря, 14:49
  • 15453
  • 0

Лямбда-функция - довольно интуитивно понятная концепция Modern C ++,  представленная в C ++ 11, поэтому в интернете уже есть множество статей по обучению лямбда-функциям. Но, тем не менее, остаются некоторые темы (такие как IIFE, типы лямбды и т.д.), о которых стоит упомянуть.

Все о лямбда-функциях в C ++ Что такое лямбда-функция?

Лямбда-выражениями называются безымянные локальные функции, которые можно создавать прямо внутри какого-либо выражения. Лямбда-выражения в C++ — это краткая форма записи анонимных функторов 

Другими словами, это просто синтаксический сахар. Синтаксис лямбда-функции определяется как:

[ capture list ] (parameters) -> return-type   {        method definition } 

Обычно  компилятор оценивает тип возврата самой лямбда-функции. Таким образом, нам не нужно явно указывать конечный тип возврата, т.е. -> return-type.

Но в некоторых сложных случаях компилятор не может определить тип возвращаемого значения, и нам нужно это указивать.

Почему мы должны использовать лямбда-функцию?

C ++ включает в себя много полезных универсальных функций, таких как std::for_each, что может быть удобно. К сожалению, они также могут быть довольно громоздкими, особенно если функтор, который вы хотите применить, уникален для конкретной функции. Рассмотрим следующий код для примера:

struct print {     void operator()(int element)     {         cout << element << endl;     } }; int main(void) {     std::vector<int> v = {1, 2, 3, 4, 5};     std::for_each(v.begin(), v.end(), print());     return 0; }

Если вы используете print один раз, в этом конкретном месте, кажется излишним писать целый класс, просто чтобы сделать что-то тривиальное и одноразовое.

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

std::for_each(v.begin(), v.end(), [](int element) { cout << element << endl; });

Как лямбда-функции работают внутри?

[&i] ( ) { std::cout << i; } // is equivalent to struct anonymous {     int &m_i;     anonymous(int &i) : m_i(i) {}     inline auto operator()() const     {        std::cout << i;     } };

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

Если вы захватите аргумент в качестве значения, то в замыкании будет создан член данных соответствующего типа.

Кроме того, вы можете объявить переменную / объект в аргументе лямбда-функции, который станет аргументом для вызова оператора, т.е. operator()

Преимущества использования лямбда-функции

  1. Нулевая стоимость абстракции. Да! Вы правильно прочитали. Лямбда не влияет на производительность и так же быстра, как обычная функция.
  2. Кроме того, код становится компактным, структурированным и выразительным.

Изучение лямбда-выражения

Захват по ссылке / значению

int main() {     int x = 100, y = 200;     auto print = [&] { // Capturing object by reference         std::cout << __PRETTY_FUNCTION__ << " : " << x << " , " << y << std::endl;     };     print();     return 0; }

Результат:

main()::<lambda()> : 100 , 200

В приведенном выше примере упомянут & в списке захвата. который захватывает переменную x & y в качестве ссылки. По аналогии, обозначает захваченное значение, которое создаст элемент данных того же типа в замыкании, и будет выполнено назначение копирования.

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

Список лямбда-захвата

В следующей таблице показаны разные варианты использования одного и того же:

Передача лямбды в качестве параметра

template <typename Functor> void f(Functor functor) {     std::cout << __PRETTY_FUNCTION__ << std::endl; } /* Or alternatively you can use this void f(std::function<int(int)> functor) {     std::cout << __PRETTY_FUNCTION__ << std::endl; }  */ int g() { static int i = 0; return i++; } int main() {     auto lambda_func = [i = 0]() mutable { return i++; };     f(lambda_func); // Pass lambda     f(g);           // Pass function }

Результат:

Function Type : void f(Functor) [with Functor = main()::<lambda(int)>] Function Type : void f(Functor) [with Functor = int (*)(int)]

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

Если вы заметили, здесь объявлена переменная. В результате каждый раз, когда вы вызываете lambda_func, она будет возвращаться и увеличиваться.

Перехват переменной в лямбда-выражении 

class Example { public:     Example() : m_var(10) {}     void func()     {         [=]() { std::cout << m_var << std::endl; }(); // IIFE     } private:     int m_var; }; int main() {     Example e;     e.func(); }

this указатель также может быть захвачен с помощью [this], [=] или [&], В любом из этих случаев члены данных класса (включая private) будут доступны, как и в обычном методе.

Как вы видете, использовано дополнительные () в конце объявления лямбда-функции, которое вызывало его сразу после объявления. Ето называется  IIFE  ( выражение для немедленного вызова функции ).

Типы C ++ лямбда-функций

Общая лямбда

const auto l = [](auto a, auto b, auto c) {}; // is equivalent to struct anonymous {     template <class T0, class T1, class T2>     auto operator()(T0 a, T1 b, T2 c) const     {     } };

Общая лямбда, представленная в C ++ 14,  может захватывать параметры с auto спецификатором.

Variadic универсальный лямбда

void print() {} template <typename First, typename... Rest> void print(const First &first, Rest &&... args) {     std::cout << first << std::endl;     print(args...); } int main() {     auto variadic_generic_lambda = [](auto... param) {         print(param...);     };     variadic_generic_lambda(1, "lol", 1.1); }

Лямбда с переменным пакетом параметров будет полезна во многих сценариях, таких как отладка, повторная работа с вводом различных данных и т.д.

mutable лямбда-функция

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

[]() mutable {} // is equivalent to struct anonymous {     auto operator()()  // call operator     {     } };

Мы уже видели пример этого выше. 

Лямбда как указатель на функцию

#include <iostream> #include <type_traits> int main() {     auto funcPtr = +[] {};     static_assert(std::is_same<decltype(funcPtr), void (*)()>::value); }

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

Возвращающие лямбда-функции высшего порядка

const auto less_than = [](auto x) {     return [x](auto y) {         return y < x;     }; }; int main(void) {     auto less_than_five = less_than(5);     std::cout << less_than_five(3) << std::endl;     std::cout << less_than_five(10) << std::endl;     return 0; }

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

constexpr лямбда-выражение

Начиная с C ++ 17, лямбда-выражение может быть объявлено как  constexpr .

constexpr auto sum = [](const auto &a, const auto &b) { return a + b; }; /*     is equivalent to     constexpr struct anonymous     {         template <class T1, class T2>         constexpr auto operator()(T1 a, T2 b) const         {             return a + b;         }     }; */ constexpr int answer = sum(10, 10);

Даже если вы не укажете constexpr оператор вызова функции будет constexpr в любом случае, если это удовлетворяет все требования функции.


0 комментариев
Сортировка:
Добавить комментарий

IT Новости

Смотреть все