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)’


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



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

940   7  

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* и преобразует обратно, когда это необходимо.

  1. To void*:

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

  2. Из void*:

    Функция Auto = static_cast( функция пустоты);

Comments

    Ничего не найдено.