Передача лямбда-захвата в качестве указателя на функцию
Можно ли передать лямбда-функцию в качестве указателя функции? Если это так, я должен делать что-то неправильно, потому что получаю ошибку компиляции.
Рассмотрим следующий пример
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, но это, похоже, не помогает.
Ответы
Ответ 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 правильно объясняет, почему лямбда не может быть передана как указатель на функцию, если она имеет перехват. Я хотел бы показать два простых решения проблемы.
-
Используйте 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; } };
}
-
Используйте лямбда-выражение, которое ничего не захватывает.
Поскольку ваш предикат на самом деле является просто логической константой, следующее быстро обойдет текущую проблему. Посмотрите этот ответ для хорошего объяснения, почему и как это работает.
// 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);