IT

람다를 함수 포인터로 전달

lottoking 2020. 5. 20. 08:01
반응형

람다를 함수 포인터로 전달


람다 함수를 함수 포인터로 전달할 수 있습니까? 그렇다면 컴파일 오류가 발생하여 잘못된 것을 수행해야합니다.

다음 예를 고려하십시오

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할 수 없으므로 함수 포인터로 전달할 수 없다는 것입니다. xconst도 만들려고했지만 도움이되지 않는 것 같습니다.


람다는 C ++ 11 표준 섹션 5.1.2 [expr.prim.lambda]에서 ( emphasis mine ) 초안 에서 캡처하지 않으면 함수 포인터로만 변환 할 수 있습니다 .

람다 캡처없는 람다의 클로저 형식에는 클로저 형식의 함수 호출 연산자와 동일한 매개 변수 및 반환 형식을 가진 함수를 가리키는 공개 비 가상적 비명 시적 const 변환 함수가 있습니다. 이 변환 함수에 의해 리턴 된 값은 호출 될 때 클로저 유형의 함수 호출 연산자를 호출하는 것과 동일한 효과를 갖는 함수의 주소입니다.

cppreference는 Lambda 함수 에 대한 섹션에서도이를 다루고 있습니다 .

따라서 다음 대안이 작동합니다.

typedef bool(*DecisionFn)(int);

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

그리고 이것도 :

typedef bool(*DecisionFn)();

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

와 같은 5gon12eder의 포인트 아웃, 당신은 또한 사용할 수 std::function있지만, 참고 std::function무거운 무게를 그 비용 덜 상충되지 않도록.


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

나는 이것을 조금 오래 알고있다 ..

그러나 나는 추가하고 싶었다 :

람다 식 (심지어 캡처 된 것)은 함수 포인터 (멤버 ​​함수에 대한 포인터)로 처리 될 수 있습니다.

Lambda 표현식은 단순한 함수가 아니기 때문에 까다 롭습니다. 실제로는 operator ()가있는 객체입니다.

당신이 창의적이라면 이것을 사용할 수 있습니다! std :: function 스타일의 "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"처럼 작동을 시작할 수있는 클래스를 만들려면 간단한 예를 들어 보겠습니다. 먼저 객체와 함수 포인터를 저장할 수있는 것보다 클래스 / 구조체가 필요하며 그것을 실행하려면 operator ()가 필요합니다.

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

Capturing lambdas cannot be converted to function pointers, as this answer pointed out.

However, it is often quite a pain to supply a function pointer to an API that only accepts one. The most often cited method to do so is to provide a function and call a static object with it.

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

This is tedious. We take this idea further and automate the process of creating wrapper and make life much easier.

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

And use it as

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

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

Live

This is essentially declaring an anonymous function at each occurrence of fnptr.

Note that invocations of fnptr overwrite the previously written callable given callables of the same type. We remedy this, to a certain degree, with the int parameter N.

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

While the template approach is clever for various reasons, it is important to remember the lifecycle of the lambda and the captured variables. If any form of a lambda pointer is is going to be used and the lambda is not a downward continuation, then only a copying [=] lambda should used. I.e., even then, capturing a pointer to a variable on the stack is UNSAFE if the lifetime of those captured pointers (stack unwind) is shorter than the lifetime of the lambda.

A simpler solution for capturing a lambda as a pointer is:

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

e.g., new std::function<void()>([=]() -> void {...}

Just remember to later delete pLamdba so ensure that you don't leak the lambda memory. Secret to realize here is that lambdas can capture lambdas (ask yourself how that works) and also that in order for std::function to work generically the lambda implementation needs to contain sufficient internal information to provide access to the size of the lambda (and captured) data (which is why the delete should work [running destructors of captured types]).


A shortcut for using a lambda with as a C function pointer is this:

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

Using Curl as exmample (curl debug info)

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

As it was mentioned by the others you can substitute Lambda function instead of function pointer. I am using this method in my C++ interface to 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);

참고URL : https://stackoverflow.com/questions/28746744/passing-capturing-lambda-as-function-pointer

반응형