Programing

캡처를 함수 포인터로 사용하는 C ++ 람다

lottogame 2020. 9. 25. 08:11
반응형

캡처를 함수 포인터로 사용하는 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)’

약간 읽은 후. 캡처 사용하는 람다는 암시 적 으로 함수 포인터 로 변환 될 수 없다는 것을 배웠습니다 .

이에 대한 해결 방법이 있습니까? "암시 적으로"변환 될 수 없다는 사실은 "명시 적으로"변환 될 수 있다는 것을 의미합니까? (성공하지 않고 캐스팅을 시도했습니다). 람다를 사용하여 항목을 일부 개체에 추가 할 수 있도록 작업 예제를 수정하는 깨끗한 방법은 무엇입니까?


람다 캡처는 상태를 보존해야하므로 단순한 "해결 방법"이 없습니다. 단순한 함수 아니기 때문 입니다. 함수 포인터의 요점은 단일 전역 함수를 가리키며이 정보에는 상태에 대한 공간이 없다는 것입니다.

가장 가까운 해결 방법 (기본적으로 상태 저장을 무시 함)은 람다 / 함수에서 액세스 할 수있는 전역 변수 유형을 제공하는 것입니다. 예를 들어, 전통적인 functor 객체를 만들고 고유 한 (전역 / 정적) 인스턴스를 참조하는 정적 멤버 함수를 제공 할 수 있습니다.

그러나 그것은 람다를 포착하는 모든 목적을 무너 뜨리는 것과 같습니다.


이 문제가 발생했습니다.

코드는 람다 캡처없이 잘 컴파일되지만 람다 캡처에는 형식 변환 오류가 있습니다.

C ++ 11의 솔루션은 사용하는 것입니다 std::function(편집 : 함수 서명을 수정할 필요가없는 다른 솔루션이이 예제 뒤에 표시됨). 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;
}

실물

Lambda 함수는 매우 편리하고 코드를 줄입니다. 제 경우에는 병렬 프로그래밍을 위해 람다가 필요했습니다. 그러나 캡처 및 함수 포인터가 필요합니다. 내 솔루션이 여기 있습니다. 그러나 캡처 한 변수의 범위에주의하십시오.

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

로컬 전역 (정적) 방법을 사용하면 다음과 같이 수행 할 수 있습니다.

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

헤헤-꽤 오래된 질문이지만 여전히 ...

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

There is a hackish way to convert a capturing lambda into a function pointer, but you need to be careful when using it:

https://codereview.stackexchange.com/questions/79612/c-ifying-a-capturing-lambda

Your code would then look like this (warning: brain compile):

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

My solution, just use a function pointer to refer to a static lambda.

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

Found an answer here: http://meh.schizofreni.co/programming/magic/2013/01/23/function-pointer-from-lambda.html

It converts lambda pointer to void* and convert back when needed.

  1. to void*:

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

  2. from void*:

    auto function = static_cast< std::function*>( voidfunction);

참고URL : https://stackoverflow.com/questions/7852101/c-lambda-with-captures-as-a-function-pointer

반응형