2015-02-26 14 views
121

Kann eine Lambda-Funktion als Funktionszeiger übergeben werden? Wenn ja, muss ich etwas falsch machen, weil ich einen Kompilierfehler bekomme.Lambda als Funktionszeiger übergeben

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

Als ich try to compile this, ich folgende Kompilierungsfehler erhalten:

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 ein Heck einer Fehlermeldung ist zu verdauen, aber ich denke, was ich bin herauskommen ist, dass das Lambda nicht als constexpr behandelt werden kann, also kann ich es nicht als Funktionszeiger übergeben? Ich habe versucht, auch x const, aber das scheint nicht zu helfen.

+20

Lambda kann nur dann zum Funktionszeiger zurückkehren, wenn sie nichts erfassen. – Jarod42

+3

http://blogs.msdn.com/b/oldnewthing/archive/2015/02/20/10594680.aspx – BoBTFish

Antwort

118

Ein Lambda kann nur auf einen Funktionszeiger umgewandelt werden, wenn sie nicht erfasst, aus dem draft C++11 standard Abschnitt 5.1.2[expr.prim.lambda] sagt (Hervorhebung von mir):

The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type’s function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type’s function call operator.

Hinweis Die cppreference behandelt dies auch in ihrem Abschnitt Lambda functions.

So würden folgende Alternativen arbeiten:

typedef bool(*DecisionFn)(int); 

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

und würde so das:

typedef bool(*DecisionFn)(); 

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

und als 5gon12eder weist darauf hin, Sie auch std::function verwenden, aber beachten Sie, dass std::function is heavy weight, so ist es kein kosten- loser Kompromiss.

54

Shafik Yaghmour's answer richtig erklärt, warum das Lambda nicht als Funktionszeiger übergeben werden kann, wenn es eine Erfassung hat. Ich möchte zwei einfache Fixes für das Problem zeigen.

  1. Verwenden std::function statt roh Funktionszeiger.

    Dies ist eine sehr saubere Lösung. Beachten Sie jedoch, dass es zusätzlichen Overhead für das Löschen des Typs (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. einen Lambda-Ausdruck verwenden, die nicht alles erfassen ist.

    Da Ihr Prädikat eigentlich nur eine boolesche Konstante ist, würde das folgende Problem schnell um das aktuelle Problem herum funktionieren. Eine gute Erklärung, warum und wie das funktioniert, finden Sie unter this answer.

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

Ich bin ziemlich erstaunt, dass dieser bedingte Ausdruck tatsächlich funktioniert ... –

+4

@ T.C. Sehen Sie diese [Frage] (http://stackoverflow.com/q/27989031) für Details, warum es funktioniert –

+0

@ShafikYaghmour Ja, ich weiß. Ich musste [over.built]/p25 überprüfen, um zu verifizieren, dass es einen eingebauten Kandidaten für Funktionszeiger gibt. –

0

Wie es von den anderen erwähnt wurde, kann man Lambda-Funktion anstelle von Funktionszeiger ersetzen. 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); 
15

weiß ich, das ein wenig alt ..

Aber ich wollte noch hinzufügen:

Lambda Ausdruck (auch erfasst sind) als Funktionszeiger behandelt werden können!

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 "Funktion" -Klasse im Stil der Std :: -Funktion. Wenn Sie das Objekt speichern!

Sie können auch den Funktionszeiger verwenden.

den Funktionszeiger zu verwenden, können Sie die folgende 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; 

eine Klasse zu erstellen, die wie eine Arbeit beginnen kann „std :: function“ Ich werde nur kurz Beispiel tun. Zuerst müssen Sie eine Klasse/Struktur als Dosenspeicherobjekt und Funktionszeiger auch 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...); 
    } 
}; 

Damit Sie jetzt gefangen laufen, noncaptured lambdas, 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:)

Greets!

Edit: entfernt Nadeln FP Vorlage entfernt Funktionszeiger Parameter,

-Update 04.07.17 bis lambda_expression umbenannt:

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

Wow Das ist unglaublich! Wir könnten also nur die inneren Zeiger von Lambda verwenden (um die Member-Funktion operator()), um gespeicherte Lambdas in einer Wrapper-Klasse aufzurufen !! TOLLE!! Warum brauchen wir dann jemals die std :: -Funktion? Und ist es möglich, lambda_expression zu machen, um diese "int" -Parameter direkt aus dem übergebenen Lambda selbst abzuleiten? – barney

+1

Ich habe eine kurze Version meines eigenen Codes hinzugefügt. das sollte mit einer einfachen automatischen f = make :: function (lambda) arbeiten; Aber ich bin ziemlich sicher, dass Sie viel Situation finden werden, wird mein Code nicht funktionieren. std :: function ist viel besser konstruiert als dies und sollte das sein, wenn Sie arbeiten. Dies hier ist für Bildung und persönlichen Gebrauch. – Noxxer

+0

sehr neugierig! Interessanterweise, wie analysiert es? Ist der Ausdruck "C (Args ...)" -Konstruktor, der das Template-Parameterpaket verwendet? Warum ist dann der resultierende Typ eine gültige Rückgabe für "operator()", soweit ich weiß, dass Konstruktoren keine Werte zurückgeben, wie also "result_of" den Rückgabetyp von operator() vom C-Typ selbst ableitet?)) – barney

1

Capturing lambdas kann nicht auf Funktionszeiger umgewandelt werden, wie this answer hingewiesen.

Es ist jedoch oft ziemlich schmerzhaft, einen Funktionszeiger an eine API zu liefern, die nur einen akzeptiert. Die am häufigsten genannte Methode besteht darin, eine Funktion bereitzustellen und ein statisches Objekt damit aufzurufen.

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

Dies ist mühsam. Wir nehmen diese Idee weiter und automatisieren den Prozess der Erstellung 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(args...)); 
    }; 
} 

template<typename Fn, int N = 0, typename Callable> 
Fn* fnptr(Callable&& c) 
{ 
    return fnptr_<N>(std::forward<Callable>(c), (Fn*)nullptr); 
} 

Und verwenden Sie 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 resultierte im Wesentlichen bei jedem Auftreten von fnptr eine anonyme Funktion wird erklärt.

Beachten Sie, dass Aufrufe von fnptr überschreiben die zuvor geschriebenen callable angegebenen Callables des gleichen Typs.Abhilfe schaffen 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 

Wenn Sie verrückt genug sind, können N zu einem constexpr counter vorbelegt werden, um die Nutzung noch weiter zu erleichtern.

Verwandte Themen