C ++ лямбда с захватами в качестве указателя на функцию - PullRequest
81 голосов
/ 21 октября 2011

Я играл с лямбдами 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)’

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

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

Ответы [ 8 ]

46 голосов
/ 25 июня 2013

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

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

Решением с C ++ 11 является использование std::function (правка: после этого примера показано другое решение, не требующее изменения сигнатуры функции). Вы также можете использовать 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;
}

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

#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;
}
39 голосов
/ 21 октября 2011

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

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

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

11 голосов
/ 10 октября 2015

ORIGINAL

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

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 ++ с перехватами в качестве указателя на функцию.Поскольку это было полезно для меня и других людей, я внес некоторые улучшения.

Стандартная функция C Указатель 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
6 голосов
/ 21 января 2018

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

template <class F>
auto cify_no_args(F&& f) {
  static F fn = std::forward<F>(f);
  return [] {
    return fn();
  };
}

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

void some_c_func(void (*callback)());

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

some_c_func(cify_no_args([&] {
  // code
}));

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

template <class F>
struct lambda_traits : lambda_traits<decltype(&F::operator())>
{ };

template <typename F, typename R, typename... Args>
struct lambda_traits<R(F::*)(Args...)> : lambda_traits<R(F::*)(Args...) const>
{ };

template <class F, class R, class... Args>
struct lambda_traits<R(F::*)(Args...) const> {
    using pointer = std::add_pointer<R(Args...)>::type;

    static pointer cify(F&& f) {
        static F fn = std::forward<F>(f);
        return [](Args... args) {
            return fn(std::forward<Args>(args)...);
        };
    }
};

template <class F>
inline typename lambda_traits<F>::pointer cify(F&& f) {
    return lambda_traits<F>::cify(std::forward<F>(f));
}

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

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;
}));
4 голосов
/ 10 июня 2015

Хе-хе - довольно старый вопрос, но все же ...

#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;
}
0 голосов
/ 10 мая 2019

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

typedef int (* MYPROC)(int);

void fun(MYPROC m)
{
    cout << m(100) << endl;
}

template<class T>
void fun2(T f)
{
    cout << f(100) << endl;
}

void useLambdaAsFunPtr()
{
    int p = 7;
    auto f = [p](int a)->int {return a * p; };

    //fun(f);//error
    fun2(f);
}

void useLambdaAsFunPtr2()
{
    int p = 7;
    static auto f = [p](int a)->int {return a * p; };
    MYPROC ff = [](int i)->int { return f(i); };
    //here, it works!
    fun(ff);
}

void test()
{
    useLambdaAsFunPtr2();
}
0 голосов
/ 05 февраля 2015

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

https://codereview.stackexchange.com/questions/79612/c-ifying-a-capturing-lambda

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

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;
}
0 голосов
/ 21 января 2015

Нашел ответ здесь: http://meh.schizofreni.co/programming/magic/2013/01/23/function-pointer-from-lambda.html

Преобразует lambda pointer в void* и преобразует обратно при необходимости.

  1. до void*:

    auto voidfunction = new decltype (to_function (lambda)) (to_function (lambda));

  2. из void*:

    auto function = static_cast ( voidfunction);

...