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

  • 2 декабря, 16:49
  • 1595
  • 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 комментариев
Сортировка:
Добавить комментарий