Требуется вводящая тело лямбды

Добавил пользователь Владимир З.
Обновлено: 20.09.2024

Привет, Хабр! Представляю вашему вниманию перевод статьи "How to start working with Lambda Expressions in Java" автора Luis Santiago.

Краткое введение

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

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

Функциональный интерфейс

Функциональный интерфейс — это интерфейс, содержащий один и только один абстрактный метод.

Если вы посмотрите на определение стандартного интерфейса Runnable, то вы заметите как он попадает в определение функционального интерфейса, поскольку он определяет только один метод: run().

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

Оператор Стрелка

Лямбда-выражения вводят новый оператор стрелка -> в Java. Он разделяет лямбда-выражение на 2 части:


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

Правая сторона — это тело выражения, которое определяет его действия.
Теперь, используя функциональные выражения и оператор стрелки можно составить просто лямбда-выражение:

Переменные morningGreeting и eveningGreeting в строках 6 и 7 соответственно в примере выше создают ссылку на интерфейс MyGreeting и определяют 2 выражения приветствия.

При написании лямбда-выражения можно явно указать тип параметра, как это делается в примере ниже:

Блок Лямбда-выражений

До сих пор я рассматривал одиночные лямбда-выражения. Существует еще один тип выражения, когда справа от оператора стрелки находится не одно простое выражение и так называемый блок лямбда:

Функциональные интерфейсы generic

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

Использование Лямбда-выражений в качестве аргументов

Одно распространенное использование лямбда — передача их в качестве аргументов.
Вы можете передавать исполняемый код аргументам методов в качестве параметров. Для этого просто убедитесь, что тип функционального интерфейса совместим с требуемым параметром.


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

Ваш браузер не поддерживает видео. Установите Microsoft Silverlight, Adobe Flash Player или Internet Explorer 9.

с помощью функции ЛЯМБДА можно создавать пользовательские функции для повторного использования и вызывать их по понятным именам. Новая функция доступна во всей книге и вызывается так же, как и основные функции Excel.

Вы можете создать функцию для часто используемой формулы, избавиться от необходимости копировать и вставлять эту формулу (что может приводить к ошибкам), а также эффективно добавлять собственные функции в основную библиотеку функций Excel. Кроме того, функция ЛЯМБДА не требует VBA, макросов или JavaScript, поэтому ее могут использовать также пользователи, не являющиеся программистами.

Как работает функция ЛЯМБДА

Синтаксис

=ЛЯМБДА([параметр1; параметр2; …;] вычисление)

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

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

Замечания

Имена и параметры функции ЛЯМБДА соответствуют правилам синтаксиса Excel в отношении именования, за одним исключением: запрещено использовать точку (.) в имени параметра. Дополнительные сведения см. в статье Имена в формулах.

При создании функции ЛЯМБДА, как и в случае с любой основной формулой Excel, необходимо придерживаться рекомендаций, например, передавать правильное количество и тип аргументов, следить за соответствием открывающих и закрывающих скобок и вводить числа в неформатированном виде. Кроме того, при использовании команды Вычислить Excel немедленно возвращает результат функции ЛЯМБДА, и вы не сможете выполнить ее пошагово. Дополнительные сведения см. в статье Обнаружение ошибок в формулах.

Создание функции ЛЯМБДА

Ниже описана пошаговая процедура, выполнив которую, вы обеспечите правильную работу функции ЛЯМБДА, которая будет выполняться аналогично основной функции Excel.

Убедитесь в правильности работы формулы, используемой вами при вычислении аргумента. Это крайне важно, так как при создании функции ЛЯМБДА вам необходимо обеспечить, чтобы формула работала надлежащим образом, и в случае возникновения ошибок или непредвиденного поведения вы могли быть уверены, что не формула является тому причиной. Дополнительные сведения см. в статье Полные сведения о формулах в Excelи Создание простой формулы в Excel.

=ЛЯМБДА-функция ([параметр1; параметр2; . ];вычисление) (вызов функции)

В приведенном ниже примере возвращается значение 2.

=LAMBDA(number, number + 1)(1)

Завершив создание функции ЛЯМБДА, переместите ее в диспетчер имен для окончательного определения. Таким образом вы сможете присвоить функции ЛЯМБДА понятное имя, добавить описание и обеспечить возможность ее повторного использования в любой ячейке книги. Вы также можете управлять функцией ЛЯМБДА точно так же, как и любым именем, например, строковой константы, диапазона ячеек или таблицы.

Последовательность действий

Выполните одно из указанных ниже действий.

В Excel для Windows выберите Формулы > Диспетчер имен.

В Excel для Mac выберите Формулы > Задать имя.

Выберите Новое, а затем введите необходимые данные в диалоговом окне Новое имя:

Введите имя функции ЛЯМБДА.

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

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

Отображается в диалоговом окне Вставить функцию и в виде подсказки (вместе с аргументом Вычисление ) при вводе формулы и использовании функции Автозаполнение формул (Intellisense).

Объект ссылки:

Введите функцию ЛЯМБДА. Нажмите F2, чтобы изменить текст и запретить автоматическую вставку ссылок на ячейки.

Сохранение функции ЛЯМБДА в Диспетчере имен

Чтобы создать функцию ЛЯМБДА, нажмите ОК.

Чтобы закрыть диалоговое окно Диспетчер имен, нажмите Закрыть.

В C++ 11 и более поздних версиях лямбда-выражение, часто называемое лямбда– — это удобный способ определения объекта анонимной функции ( замыкания) непосредственно в расположении, где оно вызывается или передается в качестве аргумента функции. Обычно лямбда-выражения используются для инкапсуляции нескольких строк кода, передаваемых алгоритмам или асинхронным функциям. В этой статье определяются лямбда-выражения и их сравнение с другими методами программирования. Он описывает их преимущества и предоставляет некоторые основные примеры.

Похожие статьи

Части лямбда-выражения

В стандарте ISO C++ демонстрируется простое лямбда-выражение, передаваемое функции std::sort() в качестве третьего аргумента:

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

An illustration of the structural elements of a lambda expression.

предложение Capture (также известное как оператор лямбда-выражения в спецификации C++).

список параметров Используемых. (Также называется лямбда-объявлением)

изменяемая спецификация Используемых.

Спецификация Exception Используемых.

замыкающий-возвращаемый тип Используемых.

Предложение Capture

Лямбда-выражение может добавлять новые переменные в тексте (в C++ 14), а также получать доступ к переменным из окружающей области или записыватьих. Лямбда-выражение начинается с предложения Capture. Он указывает, какие переменные фиксируются, а также указывает, является ли запись по значению или по ссылке. Доступ к переменным с префиксом амперсанда ( & ) осуществляется по ссылке и к переменным, к которым нет доступа по значению.

Пустое предложение фиксации ( [ ] ) показывает, что тело лямбда-выражения не осуществляет доступ к переменным во внешней области видимости.

Можно использовать режим захвата по умолчанию, чтобы указать, как фиксировать все внешние переменные, упоминаемые в теле лямбда-выражения: [&] означает, что все переменные, на которые вы ссылаетесь, захватываются по ссылке, а [=] значит, они записываются по значению. Можно сначала использовать режим фиксации по умолчанию, а затем применить для определенных переменных другой режим. Например, если тело лямбда-выражения осуществляет доступ к внешней переменной total по ссылке, а к внешней переменной factor по значению, следующие предложения фиксации эквивалентны:

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

Если предложение Capture включает запись-Default & , то ни один идентификатор в записи этого предложения записи не может иметь форму &identifier . Аналогично, если предложение Capture включает запись по умолчанию = , то ни один из этих предложений не может иметь форму =identifier . Идентификатор или this не может использоваться в предложении Capture более одного раза. В следующем фрагменте кода показаны некоторые примеры.

Захват, за которым следует многоточие, — это расширение пакета, как показано в следующем примере шаблона Variadic :

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

Visual Studio 2017 версии 15,3 и более поздних версий (доступно в /std:c++17 режиме и более поздних версиях): this указатель может быть записан по значению путем указания *this в предложении capture. Захват по значению копирует весь замыкание на каждый узел вызова, где вызывается лямбда-выражение. (Замыканием является объект анонимной функции, инкапсулирующий лямбда-выражение.) Захват по значению полезен, когда лямбда выполняется в параллельных или асинхронных операциях. Это особенно полезно на некоторых аппаратных архитектурах, таких как NUMA.

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

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

Захваты ссылок можно использовать для изменения переменных вне, но захваты значений не могут. ( mutable позволяет изменять копии, но не оригиналы.)

Захват ссылок отражает обновления переменных вне, но не фиксирует значения.

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

Обобщенная фиксация (C++14)

В C++14 вы можете объявлять и инициализировать новые переменные в предложении фиксации. Для этого не требуется, чтобы эти переменные существовали во внешней области видимости лямбда-функции. Инициализация может быть выражена в качестве любого произвольного выражения. Тип новой переменной определяется типом, который создается выражением. Эта функция позволяет собирать переменные только для перемещения (например, std::unique_ptr ) из окружающей области и использовать их в лямбда-выражении.

Список параметров

Лямбда-выражения могут записывать переменные и принимать входные параметры. Список параметров (лямбда-декларатор в стандартном синтаксисе) является необязательным и в большинстве аспектов напоминает список параметров для функции.

В C++ 14, если тип параметра является универсальным, можно использовать auto ключевое слово в качестве спецификатора типа. Это ключевое слово указывает компилятору создать оператор вызова функции в качестве шаблона. Каждый экземпляр auto в списке параметров эквивалентен отдельному параметру типа.

Поскольку список параметров является необязательным, можно опустить пустые скобки, если аргументы не передаются в лямбда-выражение и его лямбда-декларатор не содержит спецификацию Exception, завершающего-Return-Typeили mutable .

Изменяемая спецификация

Как правило, оператор вызова функции лямбда-выражения является константой по значению, но использование mutable ключевого слова отменяет это. Он не создает изменяемых элементов данных. mutable Спецификация позволяет тексту лямбда-выражения изменять переменные, захваченные по значению. В некоторых примерах, приведенных далее в этой статье, показано, как использовать mutable .

Спецификация исключений

Можно использовать noexcept спецификацию исключения, чтобы указать, что лямбда-выражение не создает никаких исключений. Как и в случае с обычными функциями, компилятор Microsoft C++ создает предупреждение C4297 , если лямбда-выражение объявляет noexcept спецификацию исключения, а тело лямбда-выражения создает исключение, как показано ниже:

Дополнительные сведения см. в разделе спецификации исключений (throw).

Возвращаемый тип

Возвращаемый тип лямбда-выражения выводится автоматически. Не обязательно использовать ключевое слово, auto если не указан завершающий возвращаемый тип. Замыкающий возвращаемый тип напоминает часть функции, возвращающей возвращаемый тип, и функцию-член. Однако тип возвращаемого значения следует списку параметров, и необходимо включить ключевое слово -> элемента trailing-return-type перед типом возвращаемого значения.

Можно опустить часть возвращаемого типа лямбда-выражения, если тело лямбды содержит только один оператор return. Или, если выражение не возвращает значение. Если тело лямбда-выражения содержит один оператор return, компилятор выводит тип возвращаемого значения из типа возвращаемого выражения. В противном случае компилятор выводит тип возвращаемого значения как void . Рассмотрим следующие примеры фрагментов кода, иллюстрирующих этот принцип:

Лямбда-выражение может создавать другое лямбда-выражение в качестве своего возвращаемого значения. Дополнительные сведения см. в разделе "лямбда-выражения более высокого порядка" в примерах лямбда-выражений.

Тело лямбды

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

Фиксированные переменные из внешней области видимости (см. выше).

Локально объявленные переменные.

Члены данных класса, объявленные внутри класса и this захваченные.

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

В следующем примере содержится лямбда-выражение, которое явно фиксирует переменную n по значению и неявно фиксирует переменную m по ссылке.

Поскольку переменная n фиксируется по значению, ее значение после вызова лямбда-выражения остается равным 0 . mutable Спецификацию n можно изменить в лямбда-выражении.

Лямбда-выражение может записывать только переменные с автоматическим длительностью хранения. Однако можно использовать переменные со статической длительностью хранения в теле лямбда-выражения. В следующем примере функция generate и лямбда-выражение используются для присвоения значения каждому элементу объекта vector . Лямбда-выражение изменяет статическую переменную для получения значения следующего элемента.

Дополнительные сведения см. в разделе Generate.

В следующем примере кода используется функция из предыдущего примера и добавляется пример лямбда-выражения, использующего алгоритм generate_n стандартной библиотеки C++. Это лямбда-выражение назначает элемент объекта vector сумме предыдущих двух элементов. mutable Ключевое слово используется, чтобы тело лямбда-выражения может изменить свои копии внешних переменных x и y , которое захватывает лямбда-выражение по значению. Поскольку лямбда-выражение захватывает исходные переменные x и y по значению, их значения остаются равными 1 после выполнения лямбда-выражения.

Дополнительные сведения см. в разделе generate_n.

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

Visual Studio 2017 версии 15,3 и более поздних версий (доступно в /std:c++17 режиме и более поздних версиях): лямбда-выражение можно объявить как constexpr (или использовать его в константном выражении), если инициализация каждого захваченного или введенного элемента данных разрешена в константном выражении.

Лямбда-выражение неявно constexpr , если его результат удовлетворяет требованиям constexpr функции:

Если лямбда-выражение неявно или неявное constexpr , то преобразование в указатель функции создает constexpr функцию:

Специально для систем Майкрософт

Лямбда-выражения не поддерживаются в следующих управляемых сущностях среды CLR: ref class , ref struct , value class или value struct .

Если вы используете модификатор, зависящий от Майкрософт, например __declspec , его можно вставить в лямбда-выражение сразу после parameter-declaration-clause . Например:

Чтобы определить, поддерживается ли определенный модификатор лямбда-выражениями, см. статью об модификаторе в разделе модификаторы, относящиеся к Microsoft .

Visual Studio поддерживает стандартную лямбда-функцию c++ 11 и лямбда-выражения без отслеживания состояния. Лямбда без отслеживания состояния преобразуется в указатель функции, который использует произвольное соглашение о вызовах.

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

Лямбда выражения, имеющая выражение в качестве текста:

Лямбда оператора, имеющая блок операторов в качестве текста:

Чтобы создать лямбда-выражение, необходимо указать входные параметры (если они есть) с левой стороны лямбда-оператора и блок выражений или операторов с другой стороны.

Лямбда-выражение может быть преобразовано в тип делегата. Тип делегата, в который может быть преобразовано лямбда-выражение, определяется типами его параметров и возвращаемым значением. Если лямбда-выражение не возвращает значение, оно может быть преобразовано в один из типов делегата Action ; в противном случае его можно преобразовать в один из типов делегатов Func . Например, лямбда-выражение, которое имеет два параметра и не возвращает значение, можно преобразовать в делегат Action . Лямбда-выражение, которое имеет два параметра и возвращает значение, можно преобразовать в делегат Func . В следующем примере лямбда-выражение x => x * x , которое указывает параметр с именем x и возвращает значение x в квадрате, присваивается переменной типа делегата:

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

При использовании синтаксиса на основе методов для вызова метода Enumerable.Select в классе System.Linq.Enumerable (например, в LINQ to Objects и LINQ to XML) параметром является тип делегата System.Func . При вызове метода Queryable.Select в классе System.Linq.Queryable (например, в LINQ to SQL) типом параметра является тип дерева выражения Expression> . В обоих случаях можно использовать одно и то же лямбда-выражение для указания значения параметра. Поэтому оба вызова Select выглядят одинаково, хотя на самом деле объект, созданный из лямбда-выражения, имеет другой тип.

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

Лямбда-выражение с выражением в правой => части оператора называется => . Выражения-лямбды возвращают результат выражения и принимают следующую основную форму.

Лямбды операторов

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

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

Лямбда-инструкции нельзя использовать для создания деревьев выражений.

Если лямбда-выражение имеет только один входной параметр, круглые скобки необязательны:

Два и более входных параметра разделяются запятыми:

Иногда компилятор не может вывести типы входных параметров. Вы можете указать типы данных в явном виде, как показано в следующем примере:

Для входных параметров все типы нужно задать либо в явном, либо в неявном виде. В противном случае компилятор выдает ошибку CS0748.

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

Если только один входной параметр имеет имя _ , для обеспечения обратной совместимости _ рассматривается как имя этого параметра в лямбда-выражении.

Асинхронные лямбда-выражения

С помощью ключевых слов async и await можно легко создавать лямбда-выражения и операторы, включающие асинхронную обработку. Например, в следующем примере Windows Forms содержится обработчик событий, который вызывает асинхронный метод ExampleMethodAsync и ожидает его.

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

Дополнительные сведения о создании и использовании асинхронных методов см. в разделе Асинхронное программирование с использованием ключевых слов Async и Await.

Лямбда-выражения и кортежи

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

Как правило, поля кортежи именуются как Item1 , Item2 и т. д. Тем не менее кортеж с именованными компонентами можно определить, как показано в следующем примере:

Лямбда-выражения со стандартными операторами запросов

Экземпляр этого делегата можно создать как Func , где int — входной параметр, а bool — возвращаемое значение. Возвращаемое значение всегда указывается в последнем параметре типа. Например, Func определяет делегат с двумя входными параметрами, int и string , и типом возвращаемого значения bool . Следующий делегат Func при вызове возвращает логическое значение, которое показывает, равен ли входной параметр 5:

В этом примере используется стандартный оператор запроса Count:

Компилятор может вывести тип входного параметра ввода; но его также можно определить явным образом. Данное лямбда-выражение подсчитывает указанные целые значения ( n ), которые при делении на два дают остаток 1.

В следующем примере кода показано, как создать последовательность, которая содержит все элементы массива numbers , предшествующие 9, так как это первое число последовательности, не удовлетворяющее условию:

В следующем примере показано, как указать несколько входных параметров путем их заключения в скобки. Этот метод возвращает все элементы в массиве numbers до того числа, значение которого меньше его порядкового номера в массиве:

Лямбда-выражения не используются непосредственно в выражениях запросов, но их можно использовать в вызовах методов в выражениях запросов, как показано в следующем примере:

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

Общие правила определения типа для лямбда-выражений формулируются следующим образом:

  • лямбда-выражение должно содержать то же число параметров, что и тип делегата;
  • каждый входной параметр в лямбда-выражении должен быть неявно преобразуемым в соответствующий параметр делегата;
  • возвращаемое значение лямбда-выражения (если таковое имеется) должно быть неявно преобразуемым в возвращаемый тип делегата.

Естественный тип лямбда-выражения

Лямбда-выражение в самом себе не имеет типа, так как система общих типов не имеет встроенного понятия "лямбда-выражение". Однако иногда бывает удобно говорить о неформальном "типе" лямбда-выражения. Под неофициальным термином "тип" понимается тип делегата или тип Expression, в который преобразуется лямбда-выражение.

Компилятор может определить parse как Func . Компилятор использует доступный делегат Func или Action , если он существует. Если нет, компилятор синтезирует тип делегата. Например, тип делегата синтезируется, если лямбда-выражение имеет параметры ref . Если лямбда-выражение имеет естественный тип, его можно присвоить менее явному типу, например System.Object или System.Delegate:

Группы методов (то есть имена методов без списков параметров) с ровно одной перегрузкой имеют естественный тип:

Не у всех лямбда-выражений есть естественный тип. Рассмотрим следующее объявление:

Компилятор не может определить тип параметра для s . Если компилятор не может определить естественный тип, необходимо объявить тип:

Явный тип возвращаемого значения

Как правило, тип возвращаемого значения лямбда-выражения является очевидным и легко выводится. Для некоторых выражений это не работает:

Атрибуты

Кроме того, вы можете добавить атрибуты во входные параметры или возвращаемое значение, как показано в следующем примере:

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

Лямбда-выражения вызываются через базовый тип делегата. Это отличается от методов и локальных функций. Метод делегата Invoke не проверяет атрибуты в лямбда-выражении. При вызове лямбда-выражения атрибуты не оказывают никакого влияния. Атрибуты лямбда-выражений полезны для анализа кода и могут быть обнаружены с помощью отражения. Одно из последствий этого решения — невозможность применить System.Diagnostics.ConditionalAttribute к лямбда-выражению.

Запись внешних переменных и области видимости переменной в лямбда-выражениях

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

Следующие правила применимы к области действия переменной в лямбда-выражениях.

  • Захваченная переменная не будет уничтожена сборщиком мусора до тех пор, пока делегат, который на нее ссылается, не перейдет в статус подлежащего уничтожению при сборке мусора.
  • Переменные, представленные в лямбда-выражении, невидимы в заключающем методе.
  • Лямбда-выражение не может непосредственно захватывать параметры in, ref или out из заключающего метода.
  • Оператор return в лямбда-выражении не вызывает возврат значения заключающим методом.
  • Лямбда-выражение не может содержать операторы goto, break или continue, если целевой объект этого оператора перехода находится за пределами блока лямбда-выражения. Если целевой объект находится внутри блока, использование оператора перехода за пределами лямбда-выражения также будет ошибкой.

Статическое лямбда-выражение не может сохранять локальные переменные или состояние экземпляров из охватывающих областей, но может ссылаться на статические элементы и определения констант.

Лямбда-выражения являются одним из наиболее мощных дополнений в C++11 и продолжают развиваться с каждым новым стандартом языка. В этой статье мы пройдемся по их истории и посмотрим на эволюцию этой важной части современного C++.


Вторая часть доступна по ссылке:
Lambdas: From C++11 to C++20, Part 2

Вступление

Я решил взять код у Томаса (с его разрешения!), описать его и создать отдельную статью.

Мы начнем с изучения C++03 и с необходимости в компактных локальных функциональных выражениях. Затем мы перейдем к C++11 и C++14. Во второй части серии мы увидим изменения в C++17 и даже взглянем на то, что произойдет в C++ 20.

С самого начала STL std::algorithms , такие как std::sort , могли принимать любой вызываемый объект и вызывать его для элементов контейнера. Однако в C++03 это предполагало только указатели на функции и функторы.

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

В качестве потенциального решения вы могли бы подумать о написании локального класса функторов — поскольку C++ всегда поддерживает этот синтаксис. Но это не работает…

Посмотрите на этот код:


Попробуйте скомпилировать его с -std=c++98 , и вы увидите следующую ошибку в GCC:

Если мы посмотрим на N3337 — окончательный вариант C++11, то увидим отдельный раздел для лямбд: [expr.prim.lambda].

Далее к C++11

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

Вы также можете проверить CppInsights, который показывает, как компилятор расширяет код:

Посмотрите на этот пример:

В этом примере компилятор преобразует:

Во что-то похожее на это (упрощенная форма):


Некоторые определения, прежде чем мы начнем:

Вычисление лямбда-выражения приводит к временному prvalue. Этот временный объект называется объектом-замыканием (closure object).

Тип лямбда-выражения (который также является типом объекта-замыкания) является уникальным безымянным non-union типом класса, который называется типом замыкания (closure type).

Несколько примеров лямбда-выражений:

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


Более того [expr.prim.lambda]:
Тип замыкания, связанный с лямбда-выражением, имеет удаленный ([dcl.fct.def.delete]) конструктор по умолчанию и удаленный оператор присваивания.

Поэтому вы не можете написать:


Это приведет к следующей ошибке в GCC:


Оператор вызова

По умолчанию это встроенный константный метод. Вы можете изменить его, указав mutable после объявления параметров:

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

  • [&] — захват по ссылке, все переменные в автоматическом хранилище объявлены в области охвата
  • [=] — захват по значению, значение копируется
  • [x, & y] — явно захватывает x по значению, а y по ссылке


Вы можете поиграться с полным примером здесь: @Wandbox

Хотя указание [=] или [&] может быть удобно — поскольку оно захватывает все переменные в автоматическом хранилище, более очевидно захватывать переменные явно. Таким образом, компилятор может предупредить вас о нежелательных эффектах (см., например, примечания о глобальных и статических переменных)

И важная цитата:

По умолчанию operator() типа замыкания является константным, и вы не можете изменять захваченные переменные внутри тела лямбда-выражения.
Если вы хотите изменить это поведение, вам нужно добавить ключевое слово mutable после списка параметров:


В приведенном выше примере мы можем изменить значения x и y… но это только копии x и y из прилагаемой области видимости.

Захват глобальных переменных

Если у вас есть глобальное значение, а затем вы используете [=] в лямбде, вы можете подумать, что глобальное значение также захвачено по значению… но это не так.


Поиграть с кодом можно здесь: @Wandbox

Захватываются только переменные в автоматическом хранилище. GCC может даже выдать следующее предупреждение:


Это предупреждение появится только в том случае, если вы явно захватите глобальную переменную, поэтому, если вы используете [=] , компилятор вам не поможет.
Компилятор Clang более полезен, так как генерирует ошибку:

Захват статических переменных

Захват статических переменных аналогичен захвату глобальных:


Поиграть с кодом можно здесь: @Wandbox


И снова, предупреждение появится, только если вы явно захватите статическую переменную, поэтому, если вы используете [=] , компилятор вам не поможет.

Захват члена класса

Знаете ли вы, что произойдет после выполнения следующего кода:


Код объявляет объект Baz, а затем вызывает foo() . Обратите внимание, что foo() возвращает лямбду (хранящуюся в std::function ), которая захватывает член класса.

Поскольку мы используем временные объекты, мы не можем быть уверены, что произойдет, при вызове f1 и f2. Это проблема висячих ссылок, которая порождает неопределенное поведение.

Опять же, если вы укажете захват явно ([s]):


Компилятор предотвратит вашу ошибку:

Move-able-only объекты

Если у вас есть объект, который может быть только перемещен (например, 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

В вышеприведенной лямбде есть два оператора return , но все они указывают на double , поэтому компилятор может вывести тип.

IIFE — Немедленно вызываемые выражения (Immediately Invoked Function Expression)

В наших примерах я определял лямбду, а затем вызвал ее, используя объект замыкания… но ее также можно вызывать немедленно:


Такое выражение может быть полезно при сложной инициализации константных объектов.

Преобразование в указатель на функцию

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

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


Поиграться с кодом можно здесь: @Wandbox

Улучшения в C++14

C++14 добавил два значительных улучшения в лямбда-выражения:

  • Захваты с инициализатором
  • Общие лямбды

Возвращаемый тип

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

Возвращаемый тип лямбды — auto, который заменяется trailing возвращаемым типом, если он предоставляется и/или выводится из операторов возврата, как описано в [dcl.spec.auto].

Захваты с инициализатором

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


Это может решить несколько проблем, например, с типами, доступными только для перемещения.

Перемещение

Теперь мы можем переместить объект в член типа замыкания:


Оптимизация

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


Захват переменной-члена

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


Поиграться с кодом можно здесь: @Wandbox

В foo() мы захватываем переменную-член, копируя ее в тип замыкания. Кроме того, мы используем auto для вывода всего метода (ранее, в C++11 мы могли использовать std::function ).

Обобщенные лямбда-выражения

Еще одно существенное улучшение — это обобщенная лямбда.
Начиная с C++14 можно написать:


Это эквивалентно использованию объявления шаблона в операторе вызова типа замыкания:


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

В этой статье мы начали с первых дней лямбда-выражений в C++03 и C++11 и перешли к улучшенной версии в C++14.

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

В следующей части статьи мы перейдем к C++17 и познакомимся с будущими фичами C++20.

Вторая часть доступна здесь:

Читайте также: