람다를 함수 포인터로 전달


210

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

다음 예를 고려하십시오

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도 만들려고했지만 도움이되지 않는 것 같습니다.


34
람다는 아무것도 캡처하지 않은 경우에만 함수 포인터로 부패 할 수 있습니다.
Jarod42


후손을 위해 위에 링크 된 블로그 게시물은 이제 devblogs.microsoft.com/oldnewthing/20150220-00/?p=44623
warrenm

답변:


205

람다는 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무거운 무게를 그 비용 덜 상충되지 않도록.


2
참고 사항 : C stuff에서 사용하는 일반적인 솔루션 중 하나 void*는 유일한 매개 변수로 a를 전달하는 것입니다 . 일반적으로 "사용자 포인터"라고합니다. 비교적 가볍지 만 malloc약간의 공간이 필요한 경향이 있습니다 .
기금 모니카의 소송

94

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

4
@TC 왜 작동하는지 자세히 알아 보려면이 질문 을 보십시오
Shafik Yaghmour

일반적으로 컴파일 타임에 캡처 데이터를 알고 있으면 데이터를 유형 데이터로 변환 한 다음 캡처가없는 람다로 돌아갈 수 있습니다. 방금 다른 질문에 쓴 을 참조하십시오 (@ 5gon12eder의 답변은 여기에 있습니다).
dan-man

포인터 함수보다 객체의 수명이 길어야합니까? 에 사용하고 싶습니다 glutReshapeFunc.
ar2015

나는이 제안을 권하지 않는다. 이러한 오류와 관련된 관행. std :: function을 사용하려면 std :: function을 사용할 수있는 모든 종류의 방법이 표시되어야합니다. 어떤 방법은 원하지 않는 것이기 때문입니다.
네거티브

1
이것은 질문에 대답하지 않습니다. 만약 std::function람다를 사용할 수 있다면 – 왜 그렇지 않을까요? 최소한 더 읽기 쉬운 구문입니다. 일반적으로 함수 포인터를 사용하여 C 라이브러리와 상호 작용하고 (실제로 외부 라이브러리와) std :: function 또는 lambda를 허용하도록 수정할 수는 없습니다.
Hi-Angel

40

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

람다 식은 단순한 함수가 아니기 때문에 까다 롭습니다. 실제로는 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와 함께 작동합니다

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

와우 대단하다! 그래서 우리는 래퍼 클래스에서 저장된 람다를 호출하기 위해 람다의 클래스 내부 포인터 (멤버 ​​함수 operator ())를 사용할 수 있습니다! 놀랄 만한!! 왜 std :: function이 필요한가? 그리고 lambda_expression <decltype (lambda), int, int, int>가 전달 된 람다 자체에서 직접 이러한 "int"매개 변수를 자동으로 추론 / 할 수 있습니까?
barney

2
내 코드의 짧은 버전을 추가했습니다. 이것은 간단한 auto f = make :: function (lambda); 그러나 나는 당신이 내 코드가 작동하지 않는 많은 상황을 찾을 것이라고 확신합니다. std :: function은 이것보다 훨씬 잘 구성되어 있으며 작업 할 때 가야합니다. 이것은 교육과 개인적인 사용을위한 것입니다.
Noxxer

14
이 솔루션에는 operator()구현을 통해 람다를 호출하는 것이 포함 되어 있으므로 올바르게 읽으면 C 스타일 함수 포인터를 사용하여 람다를 호출하는 것이 효과가 없다고 생각 합니까? 그게 원래의 질문이었습니다.
Remy Lebeau

13
람다는 당신이하지 않은 함수 포인터로 처리 할 수 ​​있다고 주장했습니다. 람다를 담을 다른 객체를 만들었습니다. 아무것도하지 않습니다. 원래 람다를 사용했을 수도 있습니다.
통행인

9
이것은 "함수 포인터로 람다를 전달하는 것"이 ​​아닙니다. 이것은 "다른 것들 중에서 함수 포인터를 포함하는 객체로 람다를 전달하는 것"입니다. 차이의 세계가 있습니다.
n. '대명사'm.

15

이 답변에서 지적했듯이 캡처 람다는 함수 포인터로 변환 할 수 없습니다 .

그러나 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주어진 호출 가능 항목 을 겹쳐 씁니다 . 우리는 이것을 어느 정도 int매개 변수로 해결 N합니다.

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

N 정수를 강제로 선언하면 컴파일 타임에 함수 포인터를 덮어 쓰지 않도록 클라이언트를 기억할 수 있습니다.
fiorentinoing

2

C 함수 포인터로 람다를 사용하는 바로 가기는 다음과 같습니다.

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

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

3
그 람다는 캡처가 없습니다. OP의 문제는 함수 포인터 유형을 추론 할 필요가없는 캡처입니다 ( +트릭이 얻는 것입니다).
Sneftel

2

템플릿 접근 방식은 여러 가지 이유로 영리하지만 람다의 수명주기와 캡처 된 변수를 기억하는 것이 중요합니다. 람다 포인터의 어떤 형태를 사용하고 람다는 하향이 아닌 경우, 복사 [=] 람다 만 사용해야합니다. 즉, 캡처 된 포인터의 수명 (스택 풀기)이 람다의 수명보다 짧은 경우 스택의 변수에 대한 포인터를 캡처하는 것은 안전하지 않습니다.

포인터로 람다를 캡처하는 간단한 솔루션은 다음과 같습니다.

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

예를 들어 new std::function<void()>([=]() -> void {...}

delete pLamdba람다 메모리가 누출되지 않도록 나중에 기억하십시오 . 여기서 람다는 람다는 람다를 캡처 할 수 있으며 (작동 방식을 스스로 물어보십시오) std::function일반적으로 람다 구현에는 람다 (및 캡처 된) 데이터의 크기에 액세스 할 수있는 충분한 내부 정보가 포함되어야합니다 ( 그렇기 때문에 delete[캡쳐 된 유형의 소멸자를 실행]해야합니다.


new-std :: function 과 함께 귀찮게하는 이유 는 이미 람다를 힙에 저장하고 delete 호출을 기억할 필요가 없습니다.
크리스 도드

0

직접적인 대답은 아니지만 "functor"템플릿 패턴을 사용하여 람다 유형의 세부 사항을 숨기고 코드를 훌륭하고 간결하게 유지하는 약간의 변형입니다.

나는 당신이 결정 클래스를 어떻게 사용하고 싶었는지 확신하지 못했기 때문에 클래스를 사용하는 함수로 클래스를 확장해야했습니다. https://godbolt.org/z/jtByqE에서 전체 예를 참조하십시오.

수업의 기본 형태는 다음과 같습니다.

template <typename Functor>
class Decide
{
public:
    Decide(Functor dec) : _dec{dec} {}
private:
    Functor _dec;
};

다음과 같이 사용되는 클래스 유형의 일부로 함수 유형을 전달하는 경우 :

auto decide_fc = [](int x){ return x > 3; };
Decide<decltype(decide_fc)> greaterThanThree{decide_fc};

다시 말하지만, 왜 당신이 x람다에 전달하는 매개 변수를 갖는 것이 더 의미 가 있는지를 확신하지 못했기 때문에 다음과 같이 사용할 수 있습니다.

int result = _dec(5); // or whatever value

완전한 예를 보려면 링크를 참조하십시오


-2

다른 사람들이 언급했듯이 함수 포인터 대신 Lambda 함수를 대체 할 수 있습니다. F77 ODE 솔버 RKSUITE에 대한 C ++ 인터페이스에서이 메소드를 사용하고 있습니다.

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