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

Передача лямбда-захвата в качестве указателя на функцию

Можно ли передать лямбда-функцию в качестве указателя функции? Если это так, я должен делать что-то неправильно, потому что получаю ошибку компиляции.

Рассмотрим следующий пример

using DecisionFn = bool(*)();

class Decide
{
public:
    Decide(DecisionFn dec) : _dec{dec} {}
private:
    DecisionFn _dec;
};

int main()
{
    int x = 5;
    Decide greaterThanThree{ [x](){ return x > 3; } };
    return 0;
}

Когда я попытаюсь скомпилировать этот файл, я получаю следующую ошибку компиляции:

In function 'int main()':
17:31: error: the value of 'x' is not usable in a constant expression
16:9:  note: 'int x' is not const
17:53: error: no matching function for call to 'Decide::Decide(<brace-enclosed initializer list>)'
17:53: note: candidates are:
9:5:   note: Decide::Decide(DecisionFn)
9:5:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'DecisionFn {aka bool (*)()}'
6:7:   note: constexpr Decide::Decide(const Decide&)
6:7:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'const Decide&'
6:7:   note: constexpr Decide::Decide(Decide&&)
6:7:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'Decide&&'

Это чертовски сообщение об ошибке для переваривания, но я думаю, что я получаю от него то, что лямбда нельзя рассматривать как constexpr, поэтому я не могу передать ее как указатель на функцию? Я попытался сделать x const, но это, похоже, не помогает.

4b9b3361

Ответ 1

Лямбда может быть преобразована в указатель на функцию только в том случае, если она не захватывает, из стандартного раздела 5.1.2 проекта С++ 11 [expr.prim.lambda] сказано (выделено мое):

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

Обратите внимание, что cppreference также описывает это в своем разделе о лямбда-функциях.

Таким образом, следующие альтернативы будут работать:

typedef bool(*DecisionFn)(int);

Decide greaterThanThree{ []( int x ){ return x > 3; } };

и так будет:

typedef bool(*DecisionFn)();

Decide greaterThanThree{ [](){ return true ; } };

и, как указывает 5gon12eder, вы также можете использовать std::function, но обратите внимание, что std::function имеет большой вес, так что это не является компромиссом без затрат.

Ответ 2

Ответ Shafik Yaghmour правильно объясняет, почему лямбда не может быть передана как указатель на функцию, если она имеет перехват. Я хотел бы показать два простых решения проблемы.

  1. Используйте std::function вместо необработанных указателей на функции.

    Это очень чистое решение. Однако обратите внимание, что он включает некоторые дополнительные издержки для стирания типа (возможно, вызов виртуальной функции).

    #include <functional>
    #include <utility>
    
    struct Decide
    {
      using DecisionFn = std::function<bool()>;
      Decide(DecisionFn dec) : dec_ {std::move(dec)} {}
      DecisionFn dec_;
    };
    
    int
    main()
    {
      int x = 5;
      Decide greaterThanThree { [x](){ return x > 3; } };
    }
    
  2. Используйте лямбда-выражение, которое ничего не захватывает.

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

    // Your 'Decide' class as in your post.
    
    int
    main()
    {
      int x = 5;
      Decide greaterThanThree {
        (x > 3) ? [](){ return true; } : [](){ return false; }
      };
    }
    

Ответ 3

Я знаю это немного старовато..

Но я хотел добавить:

Лямбда-выражение (даже захваченное) может быть обработано как указатель функции (указатель на функцию-член).

Это сложно, потому что лямбда-выражение не простая функция. На самом деле это объект с оператором().

Когда вы креативны, вы можете использовать это! Подумайте о классе "function" в стиле std :: function. Если вы сохраните объект!

Вы также можете использовать указатель на функцию.

Чтобы использовать указатель на функцию, вы можете использовать следующее:

int first = 5;
auto lambda = [=](int x, int z) {
    return x + z + first;
};
int(decltype(lambda)::*ptr)(int, int)const = &decltype(lambda)::operator();
std::cout << "test = " << (lambda.*ptr)(2, 3) << std::endl;

Чтобы создать класс, который может начать работать как "std :: function", я просто сделаю короткий пример. Во-первых, вам нужен класс/структура, которые могут хранить указатель на объект и функцию, а также оператор() для его выполнения:

// OT => Object Type
// RT => Return Type
// A ... => Arguments
template<typename OT, typename RT, typename ... A>
struct lambda_expression {
    OT _object;
    RT(OT::*_function)(A...)const;

    lambda_expression(const OT & object)
        : _object(object), _function(&decltype(_object)::operator()) {}

    RT operator() (A ... args) const {
        return (_object.*_function)(args...);
    }
};

Теперь вы можете запускать захваченные, не захваченные лямбды, как вы используете оригинал:

auto capture_lambda() {
    int first = 5;
    auto lambda = [=](int x, int z) {
        return x + z + first;
    };
    return lambda_expression<decltype(lambda), int, int, int>(lambda);
}

auto noncapture_lambda() {
    auto lambda = [](int x, int z) {
        return x + z;
    };
    return lambda_expression<decltype(lambda), int, int, int>(lambda);
}

void refcapture_lambda() {
    int test;
    auto lambda = [&](int x, int z) {
        test = x + z;
    };
    lambda_expression<decltype(lambda), void, int, int>f(lambda);
    f(2, 3);

    std::cout << "test value = " << test << std::endl;
}

int main(int argc, char **argv) {
    auto f_capture = capture_lambda();
    auto f_noncapture = noncapture_lambda();

    std::cout << "main test = " << f_capture(2, 3) << std::endl;
    std::cout << "main test = " << f_noncapture(2, 3) << std::endl;

    refcapture_lambda();

    system("PAUSE");
    return 0;
}

Этот код работает с VS2015 Надеюсь, это поможет:)

Здоровается!

Редактировать: убран шаблон игл FP, удален параметр указателя функции, переименован в lambda_expression

Обновление 04.07.17:

template <typename CT, typename ... A> struct function
: public function<decltype(&CT::operator())(A...)> {};

template <typename C> struct function<C> {
private:
    C mObject;

public:
    function(const C & obj)
        : mObject(obj) {}

    template<typename... Args> typename 
    std::result_of<C(Args...)>::type operator()(Args... a) {
        return this->mObject.operator()(a...);
    }

    template<typename... Args> typename 
    std::result_of<const C(Args...)>::type operator()(Args... a) const {
        return this->mObject.operator()(a...);
    }
};

namespace make {
    template<typename C> auto function(const C & obj) {
        return ::function<C>(obj);
    }
}

int main(int argc, char ** argv) {
   auto func = make::function([](int y, int x) { return x*y; });
   std::cout << func(2, 4) << std::endl;
   system("PAUSE");
   return 0;
}

Ответ 4

Захват лямбды не может быть преобразован в указатели на функции, как указано в этом ответе.

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

static Callable callable;
static bool wrapper()
{
    return callable();
}

Это утомительно. Мы продолжаем эту идею и автоматизируем процесс создания wrapper и делаем жизнь намного проще.

#include<type_traits>
#include<utility>

template<typename Callable>
union storage
{
    storage() {}
    std::decay_t<Callable> callable;
};

template<int, typename Callable, typename Ret, typename... Args>
auto fnptr_(Callable&& c, Ret (*)(Args...))
{
    static bool used = false;
    static storage<Callable> s;
    using type = decltype(s.callable);

    if(used)
        s.callable.~type();
    new (&s.callable) type(std::forward<Callable>(c));
    used = true;

    return [](Args... args) -> Ret {
        return Ret(s.callable(std::forward<Args>(args)...));
    };
}

template<typename Fn, int N = 0, typename Callable>
Fn* fnptr(Callable&& c)
{
    return fnptr_<N>(std::forward<Callable>(c), (Fn*)nullptr);
}

И использовать его как

void foo(void (*fn)())
{
    fn();   
}

int main()
{
    int i = 42;
    auto fn = fnptr<void()>([i]{std::cout << i;});
    foo(fn);  // compiles!
}

Жить

По сути это объявляет анонимную функцию при каждом появлении fnptr.

Обратите внимание, что вызовы fnptr перезаписывают ранее записанные callable вызовы с fnptr того же типа. Мы исправим это до некоторой степени с помощью параметра int N

std::function<void()> func1, func2;
auto fn1 = fnptr<void(), 1>(func1);
auto fn2 = fnptr<void(), 2>(func2);  // different function

Ответ 5

Хотя шаблонный подход является умным по разным причинам, важно помнить жизненный цикл лямбды и захваченных переменных. Если будет использоваться любая форма лямбда-указателя, и лямбда не является нисходящим продолжением, то должна использоваться только копирующая [=] лямбда. Т.е. даже тогда захват указателя на переменную в стеке НЕ БЕЗОПАСЕН, если время жизни этих захваченных указателей (разматывание стека) короче, чем время жизни лямбды.

Более простое решение для захвата лямбды в качестве указателя:

auto pLamdba = new std::function<...fn-sig...>([=](...fn-sig...){...});

например, new std::function<void()>([=]() → void {...}

Просто не забудьте позже delete pLamdba поэтому не delete pLamdba утечки лямбда-памяти. Секрет, который необходимо осознать, заключается в том, что лямбда-выражения могут захватывать лямбда-выражения (спросите себя, как это работает), а также, что для того, чтобы std::function работала в общем, реализация лямбды должна содержать достаточно внутренней информации, чтобы обеспечить доступ к размеру лямбды (и захваченные данные (именно поэтому delete должно работать [запуск деструкторов захваченных типов]).

Ответ 6

Как уже упоминалось другими, вы можете заменить функцию лямбда вместо указателя на функцию. Я использую этот метод в моем интерфейсе С++ для F77 ODE solver RKSUITE.

//C interface to Fortran subroutine UT
extern "C"  void UT(void(*)(double*,double*,double*),double*,double*,double*,
double*,double*,double*,int*);

// C++ wrapper which calls extern "C" void UT routine
static  void   rk_ut(void(*)(double*,double*,double*),double*,double*,double*,
double*,double*,double*,int*);

//  Call of rk_ut with lambda passed instead of function pointer to derivative
//  routine
mathlib::RungeKuttaSolver::rk_ut([](double* T,double* Y,double* YP)->void{YP[0]=Y[1]; YP[1]= -Y[0];}, TWANT,T,Y,YP,YMAX,WORK,UFLAG);

Ответ 7

Ярлык для использования лямбда с указателем на функцию C выглядит следующим образом:

"auto fun = +[](){}"

Использование Curl в качестве примера (отладочная информация curl)

auto callback = +[](CURL* handle, curl_infotype type, char* data, size_t size, void*){ //add code here :-) };
curl_easy_setopt(curlHande, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(curlHande,CURLOPT_DEBUGFUNCTION,callback);