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


94

나는 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)’

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

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


어떤 컴파일러를 사용하고 있습니까? VS10입니까?
Ramon Zarazua B.

GCC 버전 4.6.1 20110801 [GCC-4_6-지점 개정 177033] (SUSE 리눅스)
던컨

4
일반적으로 상태를 콜백에 전달하는 C 방식은 콜백에 대한 추가 인수 (일반적으로 유형 void *) 를 통해 수행됩니다 . 사용중인 라이브러리가이 추가 인수를 허용하면 해결 방법을 찾을 수 있습니다. 그렇지 않으면, 당신이하고 싶은 것을 깨끗하게 성취 할 방법이 없습니다.
Alexandre C.

예. ftw.h 및 nftw.h의 API에 결함이 있음을 알고 있습니다. 나는 fts.h하려고합니다
던컨

1
큰! /usr/include/fts.h:41:3 오류 : # 오류 "<fts.h> 64 == -D_FILE_OFFSET_BITS 사용할 수 없습니다"
던컨

답변:


48

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

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

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


3
더 깨끗한 해결책은 함수 포인터에 컨텍스트 매개 변수가 있다고 가정하여 어댑터 내부에 람다를 래핑하는 것입니다.
Raymond Chen

4
@RaymondChen : 글쎄요, 만약 당신이 함수가 어떻게 사용 될지 정의 할 자유가 있다면, 그렇습니다. 그것은 옵션입니다. 이 경우 매개 변수를 람다 자체의 인수로 만드는 것이 훨씬 더 쉬울 것입니다!
Kerrek SB

3
@KerrekSB는 전역 변수를 a에 namespace넣고으로 표시합니다 thread_local. 이것이 ftw비슷한 것을 해결하기 위해 선택한 접근 방식입니다.
Kjell Hedström 2014

"함수 포인터는 하나의 전역 함수를 가리키며이 정보는 상태에 대한 여지가 없습니다." -> 그렇다면 Java와 같은 언어가 어떻게이 작업을 수행 할 수 있습니까? 음, 물론, 그 하나의 전역 함수가 생성되기 때문에 런타임시내장 (오히려 또는 상태 참조 자신의 코드에서 그것에을). 즉 이다 요점 -이해야 하지하나의 전역 함수 그러나 여러 전역 함수 - 각 시간 람다 하나가 런타임에 사용됩니다. 그렇게하는 C ++에는 정말 아무것도 없나요? (나는 std :: function이 그 단일 목적을 위해 정확히 만들어 졌다고 생각했습니다)
Dexter

1
@Dexter : 으으 으으으 으으으 .. 짧은 대답은 '아니오'이고 긴 대답은 연산자 오버로딩을 포함합니다. 어쨌든 내 요점은 그대로입니다. Java는 C ++와 동일하지 않은 다른 언어입니다. Java에는 포인터 (또는 오버로드 가능한 호출 연산자)가 없으며 비교가 제대로 작동하지 않습니다.
Kerrek SB

47

이 문제가 발생했습니다.

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

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

72
이것은 허용되는 대답이 아니어야합니다. 요점은 변경되지 ftw걸릴 std::function... 함수 포인터 대신에
그레고리 Pakosz

이 답변에서 제안 된 두 번째 솔루션은 원래 서명을 유지하여 @ gregory-pakosz의 우려를 해결하지만 글로벌 상태를 도입하기 때문에 여전히 좋지 않습니다. ftwvoid * userdata 인수가 있으면 @ evgeny-karpov의 답변을 선호합니다.
prideout

@prideout은 동의했습니다-저도 글로벌 상태를 좋아하지 않습니다. 안타깝게도 ftw의 서명을 수정할 수없고 void * 사용자 데이터가 없다는 가정하에 상태를 어딘가에 저장해야합니다. 타사 라이브러리를 사용하여이 문제가 발생했습니다. 이것은 라이브러리가 콜백을 캡처하지 않고 나중에 사용하지 않는 한 잘 작동합니다.이 경우 전역 변수는 단순히 호출 스택에서 추가 매개 변수처럼 작동합니다. ftw의 서명을 수정할 수 있다면 void * userdata 대신 std :: function을 사용하는 것을 선호합니다.
Jay West

1
이것은 매우 복잡하고 유용한 솔루션입니다. @Gregory "작동"이라고 말해야합니다.
fiorentinoing

17

실물

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

3
이것은 람다를 C 스타일 함수 포인터로 변환하는 데 가장 편리한 솔루션입니다. 이를 인수로 취하는 함수는 상태를 나타내는 추가 매개 변수 (C 라이브러리에서 종종 "void * user"라고 명명 됨)가 필요하므로이를 호출 할 때 함수 포인터로 전달할 수 있습니다.
Codoscope

10

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

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

1
이것은 클로저 (ptr을 가져올 때) + args (호출 할 때)를 복사합니다. 그렇지 않으면 우아한 솔루션입니다
Ivan Sanz-Carasa


1
@ IvanSanz-Carasa 지적 해 주셔서 감사합니다. 클로저 유형은 CopyAssignable이 아니지만 펑 터는 있습니다. 따라서 여기에서 완벽한 전달을 사용하는 것이 좋습니다. 반면에 args의 경우 일반 C가 범용 참조를 지원하지 않으므로 많은 작업을 수행 할 수 없지만 적어도 값을 람다로 다시 전달할 수 있습니다. 이것은 추가 사본을 저장할 수 있습니다. 코드를 수정했습니다.
Vladimir Talybin

@RiaD 예, 여기서 람다는 정적 인스턴스이므로 for 루프에서 =사용하는 대신 참조로 캡처해야합니다 &i.
Vladimir Talybin

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

0

캡처하는 람다를 함수 포인터로 변환하는 hackish 방법이 있지만 사용할 때주의해야합니다.

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

0

내 솔루션은 함수 포인터를 사용하여 정적 람다를 참조하십시오.

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.