C++ lambda с захватом в качестве указателя функции
Я играл с лямбдами C++ и их неявным преобразованием в указатели функций. Мой начальный пример был использовать их в качестве обратного вызова для функции ftw. Это работает, как и ожидалось.
#include <ftw.h>
#include <iostream>
using namespace std;
int main()
{
auto callback = [](const char *fpath, const struct stat *sb,
int typeflag) -> int {
cout << fpath << endl;
return 0;
};
int ret = ftw("/etc", callback, 1);
return ret;
}
После изменения его для использования захватов:
int main()
{
vector<string> entries;
auto callback = [&](const char *fpath, const struct stat *sb,
int typeflag) -> int {
entries.push_back(fpath);
return 0;
};
int ret = ftw("/etc", callback, 1);
for (auto entry : entries ) {
cout << entry << endl;
}
return ret;
}
Я получил ошибку компилятора:
error: cannot convert ‘main()::<lambda(const char*, const stat*, int)>’ to ‘__ftw_func_t {aka int (*)(const char*, const stat*, int)}’ for argument ‘2’ to ‘int ftw(const char*, __ftw_func_t, int)’
После некоторого чтения. Я узнал, что лямбды, использующие захваты , не могут быть неявно преобразованы в указатели функций.
Есть ли обходной путь для этого? Делает ли тот факт, что они не могут быть "неявно" преобразованные означают, что они могут быть" явно " преобразованы? (Я пробовал кастинг, но безуспешно). Каков был бы простой способ изменить рабочий пример, чтобы я мог добавлять записи к какому-либо объекту с помощью лямбд?.
7 ответов:
Поскольку захват лямбд должен сохранять состояние, на самом деле не существует простого "обходного пути", поскольку они Не просто обычные функции. Суть указателя функции в том, что он указывает на единственную глобальную функцию, и эта информация не имеет места для состояния.
Ближайший обходной путь (который по существу отбрасывает статичность) заключается в предоставлении некоторого типа глобальной переменной, доступ к которой осуществляется из вашей лямбда-функции. Например, можно создать традиционный функтор объект и дать ему статическую функцию-член, которая ссылается на некоторый уникальный (глобальный/статический) экземпляр.
Но это своего рода поражение всей цели захвата лямбды.
Я только что столкнулся с этой проблемой.
Код прекрасно компилируется без лямбда-захватов, но есть ошибка преобразования типа с лямбда-захватом.
Решение с C++11 заключается в использовании
std::function(edit: после этого примера показано другое решение, не требующее изменения сигнатуры функции). Вы также можете использоватьboost::function(который на самом деле работает значительно быстрее). Пример кода-изменен так, что он будет компилироваться, компилируется сgcc 4.7.1:#include <iostream> #include <vector> #include <functional> using namespace std; int ftw(const char *fpath, std::function<int (const char *path)> callback) { return callback(fpath); } int main() { vector<string> entries; std::function<int (const char *fpath)> callback = [&](const char *fpath) -> int { entries.push_back(fpath); return 0; }; int ret = ftw("/etc", callback); for (auto entry : entries ) { cout << entry << endl; } return ret; }Править: Я должен был вернуться к этому когда я столкнулся с устаревшим кодом, в котором я не мог изменить исходную сигнатуру функции, но все равно должен был использовать лямбды. Ниже приводится решение, которое не требует изменения сигнатуры функции исходной функции:
#include <iostream> #include <vector> #include <functional> using namespace std; // Original ftw function taking raw function pointer that cannot be modified int ftw(const char *fpath, int(*callback)(const char *path)) { return callback(fpath); } static std::function<int(const char*path)> ftw_callback_function; static int ftw_callback_helper(const char *path) { return ftw_callback_function(path); } // ftw overload accepting lambda function static int ftw(const char *fpath, std::function<int(const char *path)> callback) { ftw_callback_function = callback; return ftw(fpath, ftw_callback_helper); } int main() { vector<string> entries; std::function<int (const char *fpath)> callback = [&](const char *fpath) -> int { entries.push_back(fpath); return 0; }; int ret = ftw("/etc", callback); for (auto entry : entries ) { cout << entry << endl; } return ret; }
Оригинал
Лямбда-функции очень удобны и сокращают код. В моем случае мне нужны были лямбды для параллельного программирования. Но это требует захвата и указателей функций. Мое решение здесь. Но будьте осторожны с областью действия переменных, которые вы захватили.
template<typename Tret, typename T> Tret lambda_ptr_exec(T* v) { return (Tret) (*v)(); } template<typename Tret = void, typename Tfp = Tret(*)(void*), typename T> Tfp lambda_ptr(T& v) { return (Tfp) lambda_ptr_exec<Tret, T>; }Пример
int a = 100; auto b = [&]() { a += 1;}; void (*fp)(void*) = lambda_ptr(b); fp(&b);Пример с возвращаемым значением
int a = 100; auto b = [&]() {return a;}; int (*fp)(void*) = lambda_ptr<int>(b); fp(&b);Обновить
Улучшенная версия
Прошло некоторое время с момента первого сообщения о C++ lambda с захватами в виде был размещен указатель на функцию. Поскольку он был полезен для меня и других людей, я сделал некоторые улучшения.
Стандартная функция c pointer api использует соглашение void fn(void* data). По умолчанию используется это соглашение, и лямбда-код должен быть объявлен с аргументом void*.
Улучшенная реализация
struct Lambda { template<typename Tret, typename T> static Tret lambda_ptr_exec(void* data) { return (Tret) (*(T*)fn<T>())(data); } template<typename Tret = void, typename Tfp = Tret(*)(void*), typename T> static Tfp ptr(T& t) { fn<T>(&t); return (Tfp) lambda_ptr_exec<Tret, T>; } template<typename T> static void* fn(void* new_fn = nullptr) { static void* fn; if (new_fn != nullptr) fn = new_fn; return fn; } };Exapmle
int a = 100; auto b = [&](void*) {return ++a;};Преобразование лямбды с захватами в указатель C
void (*f1)(void*) = Lambda::ptr(b); f1(nullptr); printf("%d\n", a); // 101Можно использовать и таким образом
auto f2 = Lambda::ptr(b); f2(nullptr); printf("%d\n", a); // 102В случае, если необходимо использовать возвращаемое значение
int (*f3)(void*) = Lambda::ptr<int>(b); printf("%d\n", f3(nullptr)); // 103И в случае использования данных
auto b2 = [&](void* data) {return *(int*)(data) + a;}; int (*f4)(void*) = Lambda::ptr<int>(b2); int data = 5; printf("%d\n", f4(&data)); // 108
Используя локально глобальный (статический) метод, это можно сделать следующим образом
template <class T> auto wrap(T t) { static T fn = t; return [] { fn(); }; }Предположим, что у нас есть
void some_c_func(void (*callback)());Таким образом, использование будет
some_c_func(wrap([&] { // code }));Это работает, потому что каждая лямбда имеет уникальную сигнатуру, поэтому сделать ее статичной не проблема. Следуя универсальной оболочке с переменным числом аргументов и любым типом возвращаемого значения, использующим тот же метод.
template <class T> struct lambda_traits : lambda_traits<decltype(&T::operator())> { }; template <class T, class R, class... Args> struct lambda_traits<R(T::*)(Args...) const> { typedef R (*pointer)(Args...); static pointer cify(T t) { static T fn = t; return [](Args... args) { return fn(args...); }; } }; template <class T> inline typename lambda_traits<T>::pointer cify(T t) { return lambda_traits<T>::cify(t); }И аналогичное использование
void some_c_func(int (*callback)(some_struct*, float)); some_c_func(cify([&](some_struct* s, float f) { // making use of "s" and "f" return 0; }));
Хе-хе-довольно старый вопрос, но все же...
#include <iostream> #include <vector> #include <functional> using namespace std; // We dont try to outsmart the compiler... template<typename T> int ftw(const char *fpath, T callback) { return callback(fpath); } int main() { vector<string> entries; // ... now the @ftw can accept lambda int ret = ftw("/etc", [&](const char *fpath) -> int { entries.push_back(fpath); return 0; }); // ... and function object too struct _ { static int lambda(vector<string>& entries, const char* fpath) { entries.push_back(fpath); return 0; } }; ret = ftw("/tmp", bind(_::lambda, ref(entries), placeholders::_1)); for (auto entry : entries ) { cout << entry << endl; } return ret; }
Существует хитрый способ преобразования лямбды захвата в указатель функции, но вы должны быть осторожны при его использовании:
Https://codereview.stackexchange.com/questions/79612/c-ifying-a-capturing-lambda
Ваш код будет выглядеть следующим образом (предупреждение: brain compile):
int main() { vector<string> entries; auto const callback = cify<int(*)(const char *, const struct stat*, int)>([&](const char *fpath, const struct stat *sb, int typeflag) -> int { entries.push_back(fpath); return 0; }); int ret = ftw("/etc", callback, 1); for (auto entry : entries ) { cout << entry << endl; } return ret; }
Нашел ответ здесь: http://meh.schizofreni.co/programming/magic/2013/01/23/function-pointer-from-lambda.html
Он преобразует
lambda pointerвvoid*и преобразует обратно, когда это необходимо.
To
void*:Auto voidfunction = new decltype(to_function(lambda)) (to_function(lambda));
Из
void*:Функция Auto = static_cast( функция пустоты);
Comments