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

Опубликовано: 17.05.2024

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

Рассмотрим следующий пример

Когда я попробуйте скомпилировать это , Я получаю следующую ошибку компиляции:

Это чертовски сообщение об ошибке, чтобы переварить, но я думаю, что я получаю из этого, что лямбда не может рассматриваться как constexpr поэтому я не могу передать его как указатель на функцию? Я пытался сделать x const также, но это, кажется, не помогает.

Решение

Лямбда может быть преобразована в указатель на функцию только в том случае, если проект стандарта C ++ 11 раздел 5.1.2 [Expr.prim.lambda] говорит (акцент мой):

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

Обратите внимание, что cppreference также охватывает это в их разделе Лямбда-функции .

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

и в качестве 5gon12eder указывает, что вы также можете использовать std::function , но обратите внимание, что std::function тяжелый вес , так что это не компромисс без затрат.

Другие решения

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

использование std::function вместо необработанных указателей на функции.

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

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

Поскольку ваш предикат на самом деле является просто логической константой, следующее быстро обойдет текущую проблему. Увидеть этот ответ для хорошего объяснения, почему и как это работает.

Я знаю это немного старовато ..

Но я хотел добавить:

Лямбда-выражение (даже захваченное) может быть обработано как указатель на функцию!

Это сложно, потому что лямбда-выражение не простая функция. На самом деле это объект с оператором ().

Когда вы креативны, вы можете использовать это!
Подумайте о классе «function» в стиле std :: function.
Если вы сохраните объект!

Вы также можете использовать указатель на функцию.

Чтобы использовать указатель на функцию, вы можете использовать следующее:

Этот код работает с VS2015
Надеюсь, поможет : )

Редактирование: удален шаблон игл FP, удален параметр указателя функции, переименован в lambda_expression

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

Это утомительно. Мы продолжаем эту идею и автоматизируем процесс создания wrapper и сделать жизнь намного проще.

И использовать его как

По сути это объявляет анонимную функцию при каждом появлении fnptr ,

Обратите внимание, что вызовы fnptr переписать ранее написанное callable даны вызываемые лица того же типа. Мы исправляем это, в определенной степени, с int параметр N ,

Как уже упоминалось другими, вы можете заменить лямбда-функцию вместо указателя на функцию. Я использую этот метод в своем интерфейсе C ++ для решения F77 ODE RKSUITE.

Хотя шаблонный подход является умным по разным причинам, важно помнить жизненный цикл лямбды и захваченных переменных. Если будет использоваться любая форма лямбда-указателя, и лямбда не является нисходящим продолжением, то должна использоваться только копирующая [=] лямбда. То есть даже тогда захват указателя на переменную в стеке НЕ БЕЗОПАСЕН, если время жизни этих захваченных указателей (разматывание стека) короче, чем время жизни лямбда-выражения.

Более простое решение для захвата лямбды в качестве указателя:

auto pLamdba = new std::function<. fn-sig. >([=](. fn-sig. )<. >);

например., new std::function<void()>([=]() -> void

Просто не забудьте позже delete pLamdba поэтому убедитесь, что вы не пропускаете лямбда-память.
Секрет, который нужно осознать здесь, заключается в том, что лямбды могут захватывать лямбды (спросите себя, как это работает), а также, чтобы std::function Для общей работы лямбда-реализация должна содержать достаточную внутреннюю информацию, чтобы обеспечить доступ к размеру лямбда-данных (и перехваченных) данных (вот почему delete должен работать [запуск деструкторов захваченных типов]).

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

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

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

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

  • 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 .

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

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

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

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

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

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

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

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

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

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

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

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

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

    В новом стандарте C++0x появились зымыкания. Не вдаваясь в подробности, замыкания — это такие объекты, которые позволяют создавать функции прямо в теле других функций. Если подробнее — замыкания позволяют создавать функциональные объекты — то есть объекты, для которых определён operator(). На хабре уже писали о них: например тут.

    Мне очень понравилось нововведение и я начал им пользоваться. Но только вот незадача: по смыслу, замыкания и функции — почти одно и то же, а использовать замыкания там, где должны использоваться указатели на функции, сходу не получается. По стандарту, замыкания без списка захвата должны свободно конвертироваться в указатели на функции, но на практике такого не наблюдалось, видимо ещё не реализовано. И я задался вопросом, можно ли использовать замыкания там, где используются указатели на функции?

    Рассмотрим пример. Пусть у нас уже есть функция printFunctionTable, которая позволяет распечатать таблицу значений функции, при этом аргумент пробегает значения от 1 до 10.

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

    Так же определим функцию, значения которой нам нужно распечатать:

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

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

    Для справки: decltype — это новое ключевое слово в C++0x, позволяющее определить тип выражения, то есть, в данном случае — тип нашего замыкания cube.

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

    Дополнительно хочется отметить, что мы создали только шаблон для сигнатуры int(int). По мере надобности можно добавлять и другие сигнатуры — double(int,double) к примеру, итд. Также, для удобства можно сделать макрос

    P.S. Тестировалось на компиляторе Intel C++ Compiler 11.1, по идее, должно работать в g++ 4.5 и в visual studio 2010, главное не забыть проставить при компиляции флаги, позволяющие использовать c++0x.

    Полный код программы:

    using namespace std;

    void printFunctionTable(int (*func)(int)) <
    for(int i=1;i<=10;i++) cout << func(i) << " ";
    cout << endl;
    >

    int square(int x) <
    return x*x;
    >

    template int closureToFunction(int x) <
    CL cl;
    return cl(x);
    >

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

    Приведу пример простейшей лямбда функции:

    Выражение auto myLambda означает объявление переменной с автоматически определяемым типом. Крайне удобная конструкция C++11, которая позволяет сделать ваш код более лаконичным и устойчивым к изменениям. Настоящий тип лямбда-функции слишком сложный, поэтому набирать его нецелесообразно.

    Непосредственное объявление лямбда-функции []() < std::cout << "Hello, lambda!" << std::endl; >состоит из трех частей. Первая часть (квадратные скобки [] ) позволяет привязывать переменные, доступные в текущей области видимости. Вторая часть (круглые скобки () ) указывает список принимаемых параметров лямбда-функции. Третья часть (в фигурных скобках <> ) содержит тело лямбда-функции.

    Вызов определенной лямбда-функции ничем не отличается от вызова обычной функции: myLambda() . В нашем случае на консоль будет выведено сообщение:

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

    В качестве аргумента она принимает любой объект, который можно вызвать с аргументом -5 . Более подробно о создании таких функций мы говорили, когда рассматривали указатели на функции в C++. Мы будем передавать в call() наши лямбда-функции для запуска.

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

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

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

    Обратите внимание на побочный эффект от связывания переменных с лямбда-функцией по ссылке:

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

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

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

    Допустимо и комбинирование:

    Однако замечу, что на практике лучше не использовать обобщенное привязывание через = и & , а явно обозначать необходимые переменные по одной. Иначе могут возникнуть загадочные ошибки из-за конфликтов имен.

    Когда использовать лямбда-функции?

    Один из лучших примеров правильного использования лямбда-функций связан с библиотекой алгоритмов stl . Большинство функций этой библиотеки принимают аргумент-предикат. Такой аргумент позволяет контролировать те или иные аспекты алгоритма.

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

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

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

    Я играл с лямбдами C++ и их неявным преобразованием в указатели функций. Мой начальный пример был использовать их в качестве обратного вызова для функции ftw. Это работает, как и ожидалось.

    После изменения его для использования захватов:

    Я получил ошибку компилятора:

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

    Есть ли обходной путь для этого? Делает ли тот факт, что они не могут быть "неявно" преобразованные означают, что они могут быть" явно " преобразованы? (Я пробовал кастинг, но безуспешно). Каков был бы простой способ изменить рабочий пример, чтобы я мог добавлять записи к какому-либо объекту с помощью лямбд?.

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

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

    Но это своего рода поражение всей цели захвата лямбды.

    Я только что столкнулся с этой проблемой.

    Код прекрасно компилируется без лямбда-захватов, но есть ошибка преобразования типа с лямбда-захватом.

    Решение с C++11 заключается в использовании std::function (edit: после этого примера показано другое решение, не требующее изменения сигнатуры функции). Вы также можете использовать boost::function (который на самом деле работает значительно быстрее). Пример кода-изменен так, что он будет компилироваться, компилируется с gcc 4.7.1 :

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

    Оригинал

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

    Пример

    Пример с возвращаемым значением

    Обновить

    Улучшенная версия

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

    Стандартная функция c pointer api использует соглашение void fn(void* data). По умолчанию используется это соглашение, и лямбда-код должен быть объявлен с аргументом void*.

    Улучшенная реализация

    Exapmle

    Преобразование лямбды с захватами в указатель C

    Можно использовать и таким образом

    В случае, если необходимо использовать возвращаемое значение

    И в случае использования данных

    Используя локально глобальный (статический) метод, это можно сделать следующим образом

    Предположим, что у нас есть

    Таким образом, использование будет

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

    И аналогичное использование

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