c++ - template - Übergabe der Erfassung von Lambda als Funktionszeiger




std::function lambda (5)

Das Erfassen von Lambdas kann nicht in Funktionszeiger umgewandelt werden, wie diese Antwort zeigt .

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

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

Das ist mühsam. Wir gehen diese Idee weiter und automatisieren den Prozess der wrapper und 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!
}

Live

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

Beachten Sie, dass fnptr von fnptr die zuvor geschriebenen callable gegebenen fnptr desselben Typs überschreiben. Dies beheben wir bis zu einem gewissen Grad mit dem int Parameter N

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

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


Ein Lambda kann nur dann in einen Funktionszeiger konvertiert werden, wenn es nicht erfasst wird. In Abschnitt 5.1.2 des C ++ 11-Standardentwurfs [expr.prim.lambda] heißt es:

Der Abschlusstyp für einen Lambda-Ausdruck ohne Lambda-Erfassung verfügt über eine öffentliche, nicht virtuelle, nicht explizite const- Konvertierungsfunktion in einen Zeiger auf eine Funktion mit denselben Parametern und Rückgabetypen wie der Funktionsaufrufoperator des Abschlusstyps. Der von dieser Konvertierungsfunktion zurückgegebene Wert muss die Adresse einer Funktion sein, die beim Aufrufen dieselbe Wirkung hat wie beim 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 ; } };

5gon12eder weist darauf hin, dass Sie auch die std::function . Beachten Sie jedoch, dass die std::function std::function 5gon12eder Gewicht hat und daher kein kostengünstiger Kompromiss ist.


Ich weiß das ein bisschen alt ..

Aber ich wollte hinzufügen:

Lambda-Ausdruck (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 nutzen! Stellen Sie sich eine "Funktions" -Klasse im Stil von std :: function vor. 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 bauen, die anfangen kann, wie eine "std :: function" zu arbeiten, werde ich nur ein kurzes Beispiel geben. Zuerst benötigen Sie eine Klasse / Struktur, die Objekt- und Funktionszeiger speichern kann. Außerdem benötigen Sie 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...);
    }
};

Auf diese Weise 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 Ich hoffe, es hilft:)

Grüße!

Bearbeiten: Nadelvorlage FP entfernt, Funktionszeigerparameter entfernt, 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;
}

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

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

delete pLamdba Sie daran, delete pLamdba später zu delete pLamdba damit der Lambda-Speicher nicht verloren geht. Hier ist zu erkennen, dass Lambdas Lambdas erfassen können (fragen Sie sich, wie das funktioniert) und dass die Lambda-Implementierung ausreichend interne Informationen enthalten muss, um Zugriff auf die Größe des Lambdas (und) zu erhalten, damit die std::function generisch std::function erfasste) Daten (weshalb das delete funktionieren sollte [Destruktoren erfasster Typen ausführen]).


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 Sie std::function anstelle von rohen Funktionszeigern.

    Dies ist eine sehr saubere Lösung. Beachten Sie jedoch, dass es einen zusätzlichen Overhead für das Löschen von Typen 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 umgangen. 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; }
      };
    }






function-pointers