it-swarm.com.de

Erfassungs-Lambda als Funktionszeiger übergeben

Kann man eine Lambda-Funktion als Funktionszeiger übergeben? Wenn ja, muss ich etwas falsch machen, da ein Kompilierungsfehler angezeigt wird.

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, diese zu kompilieren , erhalte ich den folgenden Kompilierungsfehler:

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 verdammte Fehlermeldung, aber ich denke, das Lambda kann nicht als constexpr behandelt werden, also kann ich es nicht als Funktionszeiger übergeben. Ich habe auch versucht, x const zu machen, aber das scheint nicht zu helfen.

149
CoryKramer

Ein Lambda kann nur dann in einen Funktionszeiger konvertiert werden, wenn es nicht aus dem draft C++ 11-Standard Abschnitt 5.1.2[expr.prim.lambda] erfasst (Hervorhebung mein):

Der Abschlusstyp für einen Lambda-Ausdruck ohne Lambda-Capture hat ein public Nicht-virtuelle nicht explizite const Konvertierungsfunktion für Zeiger Funktion mit denselben Parameter- und Rückgabetypen wie der Abschluss Funktionsaufrufoperator des Typs. Der von dieser Konvertierung zurückgegebene Wert Funktion soll die Adresse einer Funktion sein, die, wenn sie aufgerufen wird, der gleiche Effekt wie das Aufrufen des Funktionsaufrufoperators des Abschlusstyps.

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 weist darauf hin, dass Sie auch std::function verwenden können, beachten Sie jedoch, dass std::function ein hohes Gewicht ist, und ist daher kein kostengünstiger Kompromiss.

158
Shafik Yaghmour

Shafik Yaghmours Antwort erklärt richtig, warum das Lambda nicht als Funktionszeiger übergeben werden kann, wenn es ein Capture hat. Ich möchte zwei einfache Korrekturen für das Problem zeigen.

  1. Verwenden Sie std::function anstelle von rohen Funktionszeigern.

    Dies ist eine sehr saubere Lösung. Beachten Sie jedoch, dass es einen zusätzlichen Aufwand für die Typlöschung (wahrscheinlich einen virtuellen Funktionsaufruf) enthält.

    #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 eigentlich nur eine boolesche Konstante ist, könnte das folgende Problem schnell umgangen werden. Siehe diese Antwort für eine gute Erklärung, warum und wie das funktioniert.

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

Ich weiß das ein bisschen alt ..

Aber ich wollte hinzufügen: 

Lambda-Ausdruck (auch erfasste) können als Funktionszeiger 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! Denken Sie an eine "function" -Klasse im Stil von std :: function. Wenn Sie das Objekt speichern! 

Sie können 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, mache ich nur ein kurzes Beispiel. Zuerst benötigen Sie eine Klasse/Struktur, die Objekt- und Funktionszeiger speichern kann, und benötigen einen Operator (), um sie 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 nun gefangene, nicht eingefangene 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 Hoffe, es hilft:)

Grüße

Bearbeiten: entfernte Nadelvorlage FP, entfernte Funktionszeigerparameter, umbenannt in lambda_expression

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

Die Erfassung von Lambdas kann nicht in Funktionszeiger konvertiert werden, wie diese Antwort hervorgehoben hat.

Es ist jedoch oft sehr schwierig, einen Funktionszeiger für eine API bereitzustellen, die nur einen akzeptiert. Die am häufigsten genannte Methode ist das Bereitstellen einer Funktion und das Aufrufen eines statischen Objekts.

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

Das ist langweilig. Wir gehen weiter auf diese Idee und automatisieren den Prozess der Erstellung von wrapper und machen das Leben viel einfacher.

#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 erklärt im Wesentlichen eine anonyme Funktion bei jedem Auftreten von fnptr.

Beachten Sie, dass Aufrufe von fnptr die zuvor geschriebenen callable angegebenen Callables desselben Typs überschreiben. Bis zu einem gewissen Grad beheben wir dies mit dem Parameter intN.

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

Eine Abkürzung für die Verwendung eines Lambdas als C-Funktionszeiger lautet wie folgt:

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

Verwenden von Curl als Beispiel ( 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);
0
janCoffee

Obwohl der Template-Ansatz aus verschiedenen Gründen klug ist, ist es wichtig, den Lebenszyklus des Lambdas und der erfassten Variablen zu berücksichtigen. Wenn irgendeine Form eines Lambda-Zeigers verwendet werden soll und das Lambda keine Abwärtsfortsetzung 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 (Stapelabwicklung) 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 delete pLamdba Achten Sie also darauf, dass der Lambda-Speicher nicht verloren geht. Hier ist das Geheimnis zu erkennen, dass Lambdas Lambdas fangen können (fragen Sie sich, wie das funktioniert) und dies auch, um std::function Um generisch zu arbeiten, muss die Lambda-Implementierung genügend interne Informationen enthalten, um den Zugriff auf die Größe der Lambda-Daten (und der erfassten Daten) zu ermöglichen (weshalb delete funktionieren sollte [Destruktoren erfasster Typen ausführen]).

0
smallscript

Wie von den anderen erwähnt, können Sie anstelle des Funktionszeigers die Lambda-Funktion einsetzen. Ich verwende diese Methode in meiner C++ - Schnittstelle zu 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);
0
beniekg