2013-07-15 23 views
16

Ich mag würde, so etwas tun:Pass Lambda-Ausdruck Lambda Argument C++ 11

int main() 
{ 
    auto f = [/*some variables*/](/*take lambda function*/) 
    {/*something with lambda function*/}; 

    f([/*other variables*/](/*variables to be decided by f()*/) 
    {/*something with variables*/}); 
} 

Ich weiß, dass es möglich ist, eine Lambda auf eine Funktion zu übergeben, sowie zu einem Lambda. Die folgenden Werke:

int main() 
{ 
    int x=0; 
    int y=0; 
    auto f = [x,y](double (func)(int)) -> double 
    {func(0); return 0.0;}; 

    f([](int i) -> double 
    {return 0.0;}); 
} 

aber die folgenden funktioniert nicht (sobald ich die Bereichsvariablen ändern [x] hinzufügen)

int main() 
{ 
    int x=0; 
    int y=0; 
    auto f = [x,y](double (func)(int)) -> double 
    {func(0); return 0.0;} 

    f([x](int i) -> double //[x] does not work 
    {return 0.0;}); 
} 

, die den Fehler verursacht:

error: function "lambda [](double (*)(int))->double::operator()" cannot be called with the given argument list 
     argument types are: (lambda [](int)->double) 
     object type is: lambda [](double (*)(int))->double 

Würde jemand eine Idee haben, wie man das oder einen Weg um es zu beheben? Ich bin mit dem Intel-Compiler icpc (ICC) 13.1.2 mit std = C++ 11

Dank

+13

Capturing Lambdas sind nicht konvertierbar, um Zeiger zu funktionieren –

+0

warum nicht ein 'constexpr'? 'auto' ist sowieso eine Kompilierfunktion ... – user2485710

+4

@AndyProwl Was? Keine detaillierte Antwort von Ihnen innerhalb von 10 Minuten? Komm, du lässt uns hier zu hart arbeiten, und du hast nicht mal für den Tag reappt ;-) – TemplateRex

Antwort

30

Es gibt ein paar Dinge in Bezug auf Ihre Frage zu klären. Der erste ist Was ist ein Lambda?

Ein Lambda-Ausdruck ist ein einfacher Ausdruck, aus dem der Compiler einen eindeutigen Typ generiert, der nicht benannt werden kann, und gleichzeitig wird eine Instanz des Typs generiert. Wenn Sie schreiben: [](int i) { std::cout << i; } der Compiler generiert für Sie ein Typ, der grob ist:

struct __lambda_unique_name { 
    void operator()(int i) const { std::cout << i; } 
}; 

Wie Sie sehen können, ist es nicht eine Funktion ist, aber ein Typ, der operator() als const Member-Funktion implementiert. Wenn das Lambda irgendeine Erfassung durchgeführt hat, würde der Compiler Code erzeugen, um den Wert/die Referenzen zu erfassen.

als Ecke Fall für Lambdas wie die oben, wo es kein Zustand ist, erfaßt wird, ermöglicht die Sprache für eine Umwandlung von dem Lambda-Typ auf einen Zeiger mit der Signatur der operator() (minus den this Teil) funktionieren , so über das Lambda implizit in einen Zeiger umgewandelt werden kann funktionieren int nehmen und Rückkehr nichts:

void (*f)(int) = [](int i) { std::cout << i; } 

Jetzt, wo die Grundlagen in Ihrem Code angegeben, wurden, haben Sie das Lambda:

auto f = [x,y](double (func)(int)) -> double {func(0); return 0.0;}; 

Die Regeln für Parameter zu Funktionen (die auch für Lambdas gelten) legen fest, dass ein Argument nicht vom Typ Funktion sein kann, so dass das Argument zum Lambda zu einem Zeiger auf Funktion abfällt (in gleicher Weise wie ein Argument vom Typ Array Typ zerfällt in einen Zeiger):

auto f = [x,y](double (*func)(int)) -> double {func(0); return 0.0;}; 

zu einem späteren Zeitpunkt Sie eine Lambda passieren versuchen, die eine Erfassung als Argument hat. Da es ein Capture gibt, gilt die spezielle Regel nicht, und das Lambda kann nicht in einen Zeiger umgewandelt werden, der den Compilerfehler anzeigt, den Sie sehen.

Im aktuellen Standard können Sie einen von zwei Wegen gehen. Sie können Typ-Löschung verwenden, um die genaue Art der aufrufbaren Entität aus der Signatur zu entfernen:

auto f = [x,y](std::function<double(int)> func) -> double {func(0); return 0.0;}; 

Da ein std::function<double(int)> mit jeder aufrufbar Einheit mit der entsprechenden Signatur initialisiert werden kann, das wird den Lambda-Ausdrücke in der nehmen Code auf Kosten der Typ-Löschung, die normalerweise eine dynamische Zuordnung und dynamischen Versand impliziert.

Alternativ können Sie den syntaktischen Zucker löschen und das erste Lambda-Äquivalent manuell, aber generisch machen. In diesem Fall, wo das Lambda einfach ist dies könnte eine gültige Option sein:

struct mylambda { 
    template <typename F> 
    double operator()(F fn) const { 
     fn(0); return 0.0; 
    } 
} f; 
// then use the non-lambda as you tried: 
f([x](int i) -> double {return 0.0;}); 

Schließlich, wenn Sie geduldig genug sind, können Sie warten ++ 14 C, wobei (höchstwahrscheinlich hat es noch nicht gewesen ratifiziert) wird es Unterstützung für polymorph lambdas sein, die die Schaffung der oben genannten Klasse vereinfachen:

auto f = [](auto fn) { fn(0.0); return 0.0; } // unrolls to 'mylambda' above 
+1

von dem, was ich gelesen habe, und auch von [dieser Kommentar] (http://stackoverflow.com/questions/7951377/what-is-the-type-of-lambda-when-deduced-with-auto-in-c11#comment14872356_8050769) die 'std :: function' ist ein langsamer Mechanismus als die Schablonentechnik Sie zeigen, obwohl einige sagen, dass dies zu einem virtuellen Call fällig ist, während Sie sagen, dass es eine dynamische Zuordnung zurückzuführen ist (obwohl Sie auch dynamische Dispatch sagen, das ist ich gehe davon aus gleichbedeutend mit 'virtual'). In der Regel würden Sie empfehlen die Verwendung von Vorlage? – johnbakers

5

Versuchen std :: function:

#include <functional> 
int main() 
{ 
    int x=0; 
    int y=0; 
    auto f = [x,y](std::function<double(int)> func) -> double 
      {func(0); return 0.0;}; 

    f([x](int i) -> double {return 0.0;}); 
} 
+0

Danke für eine schnelle Antwort Ich habe versucht, das zu verwenden, aber der Intel Compiler unterstützt es nicht wie hier gezeigt [http://stackoverflow.com/questions/6527064/stdfunction-and-the-intel-compiler-version-11-1]. Die Boost/Funktion-Bibliothek könnte eine mögliche Alternative sein, aber ich möchte versuchen, es zu vermeiden.Was könnte eine Alternative sein? – Couchy311

+1

Aktualisieren/wechseln Sie Ihren Compiler. Sie können nicht nur Variablen in rohe Funktionszeiger aufnehmen, Sie benötigen ein Objekt dafür. Wenn Ihr Compiler lambdas zur Verfügung stellt, aber keine Möglichkeit, diese zu speichern (was der Hauptzweck von std :: function ist), haben Sie keine Unterstützung für lambdas in Ihrem Compiler. – DanielKO

+0

hmmm ... wahr ist, sollte ich wohl einen Weg, um lambdas finden nur :( – Couchy311

1

Sie etwas versuchen könnte, wie folgendes, wenn Sie den Typ der Lambda vorher wissen, zum Beispiel:

int main() 
{ 
    int x = 0, y = 0; 

    auto f = [x](int i)->double { 
     return (double)x; 
    }; 

    auto f2 = [x,y](decltype(f) func)->double { 
     return func(0); 
    }; 

    f2(f); 

    return 0; 
} 

Oder Alternative könnten Sie die <functional> Bibliothek verwenden fo ra mehr generische Lösung, zum Beispiel:

auto f = [x,y](std::function<double(int)> func) { /* Do stuff */ }; 
3

Sie haben einfach den sauren Apfel beißen und Ihre eigene functors zu implementieren, wie wir in den dunklen Zeiten taten

struct F { 
    int x; 
    int y; 

    F(int x_, int y_) : x(x_), y(y_) {} 

    template <typename G> 
    double operator() (G&& g) const { 
     g(0); 
     return 0.0; 
    } 
}; 

#include <iostream> 

int main() 
{ 
    int x = 0; 
    int y = 0; 
    auto f = F(x, y); 

    f([x](int i){return 0.0;}); 
    f([](int i){std::cout << i << std::endl;}); 
} 

Das sollte man weiter, bis Sie Compiler unterstützt C++ 14 generische Lambdas.

+0

Was es der Zweck oder Nutzen der Verwendung eines rvalue Referenz? (Dh Wie ist '(G && g)' besser als '(G g)' ? –

+1

@ChrisBecke einen Parameter annimmt durch Bezugnahme ohne Umleitung ist idiomatisch für „ich mag möglicherweise dieses Argument mutieren und zu Wertkategorie blind sein.“ – Casey

0

Sie einen Fang Lambda cify kann, aber diese Lösung hat seine Grenzen:

#include <new> 

#include <utility> 

namespace 
{ 

template <typename F, int I, typename L, typename R, typename ...A> 
inline F cify(L&& l, R (*)(A...) noexcept(noexcept(
    std::declval<F>()(std::declval<A>()...)))) 
{ 
    static L l_(std::forward<L>(l)); 
    static bool full; 

    if (full) 
    { 
    l_.~L(); 

    new (static_cast<void*>(&l_)) L(std::forward<L>(l)); 
    } 
    else 
    { 
    full = true; 
    } 

    return [](A... args) noexcept(noexcept(
     std::declval<F>()(std::forward<A>(args)...))) -> R 
    { 
     return l_(std::forward<A>(args)...); 
    }; 
} 

} 

template <typename F, int I = 0, typename L> 
inline F cify(L&& l) 
{ 
    return cify<F, I>(std::forward<L>(l), F()); 
} 


int main() 
{ 
    int x=0; 
    int y=0; 
    auto f = [x,y](double (func)(int)) -> double 
    {func(0); return 0.0;}; 

    f(cify<double(*)(int i)>([x](int i) -> double //works now 
    {return 0.0;})); 
} 

Click für ein funktionierendes Beispiel.