Übergabe der Erfassung von Lambda als Funktionszeiger


210

Ist es möglich, eine Lambda-Funktion als Funktionszeiger zu übergeben? Wenn ja, muss ich etwas falsch machen, weil ich einen Kompilierungsfehler erhalte.

Betrachten Sie das folgende Beispiel

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

Wenn ich versuche, dies zu kompilieren , wird der folgende Kompilierungsfehler angezeigt:

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&&'

Das ist eine verdammt gute Fehlermeldung, aber ich denke, was ich daraus mache, ist, dass das Lambda nicht als solches behandelt werden constexprkann, also kann ich es nicht als Funktionszeiger übergeben? Ich habe auch versucht, xconst zu machen, aber das scheint nicht zu helfen.


34
Lambda kann nur dann in einen Funktionszeiger zerfallen, wenn sie nichts erfassen.
Jarod42


Für die Nachwelt lebt der oben verlinkte Blog-Beitrag jetzt unter devblogs.microsoft.com/oldnewthing/20150220-00/?p=44623
warrenm

Antworten:


205

Ein Lambda kann nur auf einen Funktionszeiger umgewandelt werden , wenn dies nicht der Fall zu erfassen, aus dem Entwurf C ++ 11 - Standard Abschnitt 5.1.2 [expr.prim.lambda] sagt ( Hervorhebung von mir ):

Der Schließungstyp für einen Lambda-Ausdruck ohne Lambda-Erfassung verfügt über eine öffentliche nicht virtuelle nicht explizite const- Konvertierungsfunktion für den Zeiger auf eine Funktion mit denselben Parameter- und Rückgabetypen wie der Funktionsaufrufoperator des Schließungstyps. Der von dieser Konvertierungsfunktion zurückgegebene Wert ist die Adresse einer Funktion, die beim Aufrufen den gleichen Effekt hat wie das Aufrufen des Funktionsaufrufoperators des Schließungstyps.

Beachten Sie, dass cppreference dies auch in ihrem Abschnitt über Lambda-Funktionen behandelt .

Die folgenden Alternativen würden also funktionieren:

typedef bool(*DecisionFn)(int);

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

und so würde dies:

typedef bool(*DecisionFn)();

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

und wie 5gon12eder hervorhebt , können Sie auch verwenden std::function, aber beachten Sie, dass dies ein hohes std::functionGewicht ist , so dass es kein kostengünstiger Kompromiss ist.


2
Randnotiz: Eine übliche Lösung, die von C-Sachen verwendet wird, besteht darin, a void*als einzigen Parameter zu übergeben. Es wird normalerweise als "Benutzerzeiger" bezeichnet. Es ist auch relativ leicht, erfordert aber in der Regel mallocetwas Platz.
Fund Monica Klage

94

Die Antwort von Shafik Yaghmour erklärt richtig, warum das Lambda nicht als Funktionszeiger übergeben werden kann, wenn es eine Erfassung hat. Ich möchte zwei einfache Lösungen für das Problem zeigen.

  1. Verwenden std::function anstelle von Rohfunktionszeigern.

    Dies ist eine sehr saubere Lösung. Beachten Sie jedoch, dass es zusätzlichen Aufwand für das Löschen des Typs enthält (wahrscheinlich ein virtueller Funktionsaufruf).

    #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. Verwenden Sie einen Lambda-Ausdruck, der nichts erfasst.

    Da Ihr Prädikat wirklich nur eine boolesche Konstante ist, würde das folgende Problem schnell behoben. In dieser Antwort finden Sie eine gute Erklärung, warum und wie dies funktioniert.

    // Your 'Decide' class as in your post.
    
    int
    main()
    {
      int x = 5;
      Decide greaterThanThree {
        (x > 3) ? [](){ return true; } : [](){ return false; }
      };
    }

4
@TC Siehe diese Frage für Details, warum es funktioniert
Shafik Yaghmour

Beachten Sie, dass Sie, wenn Sie die Erfassungsdaten zur Kompilierungszeit kennen, diese im Allgemeinen in Typdaten konvertieren können und dann wieder ein Lambda ohne Erfassung haben - siehe diese Antwort , die ich gerade auf eine andere Frage geschrieben habe (dank @ 5gon12eders Antwort hier).
Dan-Man

Sollte das Objekt dann nicht eine längere Lebensdauer als die Zeigerfunktion haben? Ich würde es gerne benutzen für glutReshapeFunc.
ar2015

Ich empfehle diesen Vorschlag nicht, Dinge, die dazu neigen, magisch zu funktionieren, führen zu neuen Fehlern. und Praktiken, die mit diesen Fehlern einhergehen. Wenn Sie die Funktion std :: verwenden möchten, sollten Sie alle Arten von Möglichkeiten sehen, wie die Funktion std :: verwendet werden kann. weil einige Möglichkeiten vielleicht etwas, das Sie nicht wollen.
TheNegative

1
Dies beantwortet die Frage nicht. Wenn man std::functionein Lambda benutzen könnte - warum würden sie es nicht? Zumindest ist es eine besser lesbare Syntax. Normalerweise muss ein Funktionszeiger verwendet werden, um mit C-Bibliotheken (tatsächlich mit jeder externen Bibliothek) zu interagieren , und Sie können ihn nicht ändern, um eine std :: -Funktion oder ein Lambda zu akzeptieren.
Hi-Angel

40

Lambda-Ausdrücke, auch erfasste, können als Funktionszeiger (Zeiger auf Elementfunktion) behandelt werden.

Es ist schwierig, weil ein Lambda-Ausdruck keine einfache Funktion ist. Es ist eigentlich ein Objekt mit einem Operator ().

Wenn Sie kreativ sind, können Sie dies verwenden! Stellen Sie sich eine "Funktions" -Klasse im Stil von std :: function vor. Wenn Sie das Objekt speichern, können Sie auch den Funktionszeiger verwenden.

Um den Funktionszeiger zu verwenden, können Sie Folgendes verwenden:

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;

Um eine Klasse zu erstellen, die wie eine "std :: function" arbeiten kann, benötigen Sie zunächst eine Klasse / Struktur, in der Objekt- und Funktionszeiger gespeichert werden können. Außerdem benötigen Sie einen operator (), um es auszuführen:

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

Damit können Sie jetzt erfasste, nicht erfasste Lambdas ausführen, genau wie Sie das Original verwenden:

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

Dieser Code funktioniert mit VS2015

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

Wow, das ist beeindruckend! Wir könnten also nur die inneren Zeiger der Lambda-Klasse (auf den Member-Funktionsoperator ()) verwenden, um gespeicherte Lambdas in einer Wrapper-Klasse aufzurufen !! TOLLE!! Warum brauchen wir dann jemals die Funktion std ::? Und ist es möglich, lambda_expression <decltype (lambda), int, int, int> zu machen, um / diese "int" -Parameter automatisch direkt aus dem übergebenen Lambda selbst abzuleiten?
Barney

2
Ich habe eine Kurzversion meines eigenen Codes hinzugefügt. Dies sollte mit einem einfachen Auto funktionieren. f = make :: function (lambda); Aber ich bin mir ziemlich sicher, dass Sie viele Situationen finden werden, in denen mein Code nicht funktioniert. Die std :: -Funktion ist weitaus besser aufgebaut und sollte bei der Arbeit genau richtig sein. Dies hier ist für Bildung und persönlichen Gebrauch.
Noxxer

14
Diese Lösung beinhaltet das Aufrufen des Lambda über eine operator()Implementierung. Wenn ich es also richtig lese, denke ich nicht, dass es funktionieren würde, das Lambda mit einem Funktionszeiger im C-Stil aufzurufen , oder? Darum ging es in der ursprünglichen Frage.
Remy Lebeau

13
Sie haben behauptet, dass Lambdas als Funktionszeiger behandelt werden können, was Sie nicht getan haben. Sie haben ein anderes Objekt erstellt, um ein Lambda aufzunehmen, das nichts bewirkt. Sie hätten einfach das ursprüngliche Lambda verwenden können.
Passant Bis zum

9
Dies ist nicht "Übergabe der Erfassung von Lambda als Funktionszeiger". Dies ist "Übergeben der Erfassung von Lambda als Objekt, das unter anderem einen Funktionszeiger enthält". Es gibt eine Welt voller Unterschiede.
n. 'Pronomen' m.

15

Das Erfassen von Lambdas kann als Antwort nicht in Funktionszeiger konvertiert werden hervorgehoben.

Es ist jedoch oft sehr mühsam, einen Funktionszeiger für eine API bereitzustellen, die nur einen akzeptiert. Die am häufigsten genannte Methode hierfür besteht darin, eine Funktion bereitzustellen und ein statisches Objekt damit aufzurufen.

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

Das ist langweilig. Wir bringen diese Idee weiter und automatisieren den Prozess der Erstellung wrapperund erleichtern das Leben erheblich.

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

Und benutze es als

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

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

Leben

Dies deklariert im Wesentlichen eine anonyme Funktion bei jedem Auftreten von fnptr .

Beachten Sie, dass Aufrufe zum fnptrÜberschreiben der zuvor geschriebenen callableangegebenen Callables desselben Typs. Abhilfe schaffen wir bis zu einem gewissen Grad mit dem intParameter N.

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

Das Erzwingen der Deklaration der N-Ganzzahl wäre eine elegante Möglichkeit, sich an den Client zu erinnern, um ein Überschreiben der Funktionszeiger beim Kompilieren zu vermeiden.
Fiorentinoing

2

Eine Verknüpfung für die Verwendung eines Lambda als C-Funktionszeiger lautet wie folgt:

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

Verwenden von Curl als Beispiel ( Curl-Debug-Informationen )

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
Das Lambda hat keine Gefangennahme. Das Problem des OP ist die Erfassung, ohne dass der Funktionszeigertyp abgeleitet werden muss (was +Ihnen der Trick bringt).
Sneftel

2

Obwohl der Template-Ansatz aus verschiedenen Gründen clever ist, ist es wichtig, sich an den Lebenszyklus des Lambda und die erfassten Variablen zu erinnern. Wenn irgendeine Form eines Lambda-Zeigers verwendet werden soll und das Lambda keine Fortsetzung nach unten ist, sollte nur ein kopierendes [=] Lambda verwendet werden. Das heißt, selbst dann ist das Erfassen eines Zeigers auf eine Variable auf dem Stapel UNSICHER, wenn die Lebensdauer dieser erfassten Zeiger (Abwickeln des Stapels) kürzer als die Lebensdauer des Lambda ist.

Eine einfachere Lösung zum Erfassen eines Lambda als Zeiger ist:

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

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

Denken Sie daran, später darauf zu delete pLamdbaachten, dass der Lambda-Speicher nicht verloren geht. Das Geheimnis, das hier zu erkennen ist, ist, dass Lambdas Lambdas erfassen können (fragen Sie sich, wie das funktioniert) und dass std::functiondie Lambda-Implementierung ausreichende interne Informationen enthalten muss, um auf die Größe der Lambda-Daten (und der erfassten Daten) zugreifen zu können, um generisch arbeiten zu können ( Aus diesem Grund deletesollte das funktionieren [Destruktoren von erfassten Typen ausführen]).


Warum sich mit der newFunktion - std :: beschäftigen, speichert das Lambda bereits auf dem Heap UND vermeidet, dass Sie sich daran erinnern müssen, delete aufzurufen.
Chris Dodd

0

Keine direkte Antwort, aber eine kleine Variation, um das "functor" -Vorlagenmuster zu verwenden, um die Besonderheiten des Lambda-Typs zu verbergen und den Code schön und einfach zu halten.

Ich war mir nicht sicher, wie Sie die Entscheidungsklasse verwenden wollten, daher musste ich die Klasse um eine Funktion erweitern, die sie verwendet. Das vollständige Beispiel finden Sie hier: https://godbolt.org/z/jtByqE

Die Grundform Ihrer Klasse könnte folgendermaßen aussehen:

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

Wo Sie den Typ der Funktion als Teil des verwendeten Klassentyps übergeben:

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

Auch hier war ich mir nicht sicher, warum Sie es erfassen. xEs war (für mich) sinnvoller, einen Parameter zu haben, den Sie an das Lambda übergeben, damit Sie Folgendes verwenden können:

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

Ein vollständiges Beispiel finden Sie unter dem Link


-2

Wie von den anderen erwähnt, können Sie die Lambda-Funktion anstelle des Funktionszeigers einsetzen. Ich verwende diese Methode in meiner C ++ - Schnittstelle zum 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);
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.