2016-05-22 12 views
7

Ich habe versucht, eine Lambda-Funktion einen Wert durch Referenz ohne eine Kopie des referenzierten Wertes zu machen. Mein Codebeispiel veranschaulicht das Problem. Es kompiliert und läuft ok, aber mit der "//" kommentierten Zeile anstelle der Zeile oben, tut es nicht. Ich habe zwei Lösungen gefunden (beide in meinem Beispiel dargestellt):Lambda Fct Rückkehr Referenz

  • wickeln Sie das Ergebnis mit std :: ref()
  • einen Zeiger anstelle eines Verweises

Aber beide Abhilfen sind nicht, was ich wirklich will, und ich verstehe nicht, warum sie notwendig sind: Der Ausdruck "makeRefA()" hat bereits den Typ, den die Lambda-Funktion zurückgibt (const A &) und muss daher weder kopiert noch konvertiert werden. Übrigens: Der Kopierkonstruktor wird wirklich genannt, wenn ich ihn nicht explizit lösche (was in meinem "echten" Code ein Performance-Problem ist). Für mich sieht es aus wie ein Compiler-Bug, aber ich habe mit mehreren C++ 11-Compiler versucht, die alle den gleichen Fehler zeigen. Gibt es also etwas Besonderes bezüglich der "Return" -Anweisung in einer Lambda-Funktion?

#include <functional> 
#include <iostream> 

struct A { 
    A(int i) : m(i) { } 
    A(const A&) = delete; 
    int m; 
}; 

void foo(const A & a) { 
    std::cout << a.m <<'\n'; 
} 

const A & makeRefA() { 
    static A a(3); 
    return a; 
} 

int main() { 
    std::function<const A&()> fctRef = [&] 
    { return std::ref(makeRefA()); }; //compiles ok 
    //{ return makeRefA(); }; //error: use of deleted function 'A::A(const A&)' 
    foo(fctRef()); 

    std::function<const A*()> fctPtr = 
    [&] { return &makeRefA(); }; 
    foo(*fctPtr()); 

    return 0; 
} 

Ausgang:

3 
3 

Antwort

7

standardmäßig der automatisch abgeleitete Typ eines Lambda ist die Nicht-Referenz-Version eines Typs

... der Rückgabetyp ist der Typ des zurückgegebenen expressi on (nach lvalue-to-rvalue, impliziter Konvertierung von Array zu Pointer oder Funktion zu Pointer); (source)

Wenn Sie einen Rückgabetyp mit einer Referenz wünschen, müssen Sie ihn expliziter angeben. Hier sind einige Möglichkeiten:

[&]() 
-> decltype(makeRefA()) 
{ return makeRefA()); }; 

oder einfach sein ganz persönliches Vertrauen über den Rückgabetyp mit ->

[&]() 
-> const A& 
{ return makeRefA(); } 

Wenn C++ 14 verwendet wird, dann einfach decltype(auto) verwenden,

[&]() 
-> decltype(auto) 
{ return makeRefA(); } 

Die Regeln für decltype können manchmal kompliziert sein. Aber die Tatsache, dass makeRefA() ein Ausdruck ist (im Gegensatz zur einfachen Benennung einer Variablen), bedeutet, dass der Typ des Ausdrucks (const A&) genau von zurückgegeben wird.

+0

"Es wird eine Referenz zurückgeben, wenn der Ausdruckstyp eine Referenz ist" <- Korrekt-isch. Der Ausdruck * type * wird niemals eine Referenz sein, aber Sie erhalten die richtige Antwort, wenn Sie so darüber nachdenken. – Barry

+0

Am besten Antwort, danke! – Stefan

+1

Es funktioniert auch ohne die zusätzlichen Klammern, dh std :: function fctRef = [&]() -> declltype (makeRefA()) {return makeRefA();}; – Stefan

7

Sie den

Rückgabetyp angeben
#include <functional> 
#include <iostream> 

struct A { 
    A(int i) : m(i) { } 
    A(const A&) = delete; 
    int m; 
}; 

void foo(const A & a) { 
    std::cout << a.m <<'\n'; 
} 

const A & makeRefA() { 
    static A a(3); 
    return a; 
} 

int main() { 
    std::function<const A&()> fctRef = [&]()->const A& 
// { return std::ref(makeRefA()); }; //compiles ok 
    { return makeRefA(); }; // works 
    foo(fctRef()); 

    std::function<const A*()> fctPtr = 
    [&] { return &makeRefA(); }; 
    foo(*fctPtr()); 

    return 0; 
} 
+0

Wow irgendwie total über den statischen Teil breezed:/ –

+0

Ok danke, das ist eine andere Problemumgehung, aber warum ist das notwendig für eine Funktion "const A &" zurück, während für eine Funktion "const A *" zurückgibt es nicht? – Stefan

+0

@stefan: Es ist kein "Workaround". Unter bestimmten Umständen dürfen Sie den Rückgabetyp einer Lambda-Funktion weglassen. Dies ist keiner dieser Umstände. Aus demselben Grund macht 'auto a = b' 'a' keinen Bezug auf' b'. – rici

2

Nach http://en.cppreference.com/w/cpp/language/lambda gelten diese Regeln Lambdas ohne nachlaufRückgabeTyp:

  • In C++ 11, L-Wert-zu-R-Wert, array-zu-Zeiger oder Funktion-zu-Zeiger implizit Die Konvertierung wird auf den Typ des zurückgegebenen Ausdrucks angewendet. (Hier ist die lvalue-to-rvalue-Konvertierung, was Sie trifft.)
  • In C++ 14 und später wird der Typ abgeleitet wie für eine Funktion, deren Rückgabetyp deklariert ist; und das wiederum folgt den Regeln für die Ableitung von Template-Argumenten. Da auto keine Referenzspezifikation enthält, bedeutet dies, dass Referenzen und CV-Qualifikationsmerkmale ignoriert werden.

Der Effekt ist wahrscheinlich wünschenswert, in den meisten Situationen: zum Beispiel in diesem Lambda-Ausdruck

[](const std::vector<int>& v) { return v[0]; } 

Sie wahrscheinlich eine int, obwohl std::vector<int>::operator[] const kehrt const int& zurückkehren möchten.

Wie andere erwähnt haben, können Sie dieses Verhalten überschreiben, indem Sie einen expliziten Trailing-Rückgabetyp angeben.