Подтвердить что ты не робот

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

Я играл с lambdas С++ и их неявное преобразование в указатели функций. Мой стартовый пример использовал их как обратный вызов для функции 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 с использованием захватов не может быть неявно преобразован в указатели функций.

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

4b9b3361

Ответ 1

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

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

Но этот вид побеждает всю цель захвата лямбда.

Ответ 2

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

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

Решение с С++ 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;
}

Ответ 3

ОРИГИНАЛ

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

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

Ответ 4

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

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;
}));

Ответ 5

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

#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;
}

Ответ 6

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

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;
}

Ответ 7

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

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();
}

Ответ 8

Найден ответ здесь: 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 function = static_cast < станд:: функция * > (       voidfunction);

Ответ 9

У меня была аналогичная проблема в Visual Studio 2010, и я нашел обходной путь. Я не уверен, будет ли он работать с захватами, потому что я не тестировал этот сценарий, но я не понимаю, почему это не так.

Сообщите мне, если это не сработает, и я посмотрю, смогу ли я это исправить.