C++ лямбда с захватами в качестве указателя функции


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

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

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

7 77

7 ответов:

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

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

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

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

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

решение с 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

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

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. до void*:

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

  2. С void*:

    auto function = static_cast( voidfunction);