Lambda-Erfassung als konstante Referenz?

166

Ist es möglich, durch konstante Referenz in einem Lambda-Ausdruck zu erfassen?

Ich möchte, dass die unten markierte Zuweisung fehlschlägt, zum Beispiel:

#include <cstdlib>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

int main()
{
    string strings[] = 
    {
        "hello",
        "world"
    };
    static const size_t num_strings = sizeof(strings)/sizeof(strings[0]);

    string best_string = "foo";

    for_each( &strings[0], &strings[num_strings], [&best_string](const string& s)
      {
        best_string = s; // this should fail
      }
    );
    return 0;
}

Update: Da dies eine alte Frage ist, kann es hilfreich sein, sie zu aktualisieren, wenn in C ++ 14 Funktionen vorhanden sind, die Ihnen dabei helfen. Ermöglichen die Erweiterungen in C ++ 14 das Erfassen eines Nicht-Const-Objekts als Const-Referenz? ( August 2015 )

John Dibling
quelle
sollte dein Lambda nicht so aussehen : [&, &best_string](string const s) { ...}?
Erjot
3
wirklich inkonsistente Erfassung. "const &" kann sehr nützlich sein, wenn Sie ein großes const-Objekt haben, auf das zugegriffen werden soll, das jedoch in der Lambda-Funktion nicht geändert wird
sergtk
Blick auf den Code. Sie können ein Lambda mit zwei Parametern verwenden und das zweite als konstante Referenz binden. ist allerdings mit Kosten verbunden.
Alex
1
Dies scheint in C ++ 11 nicht möglich zu sein. Aber vielleicht können wir diese Frage für C ++ 14 aktualisieren - gibt es Erweiterungen, die dies ermöglichen? Das C ++ 14 verallgemeinerte Lambda erfasst?
Aaron McDaid

Antworten:

127

const ist nicht in der Grammatik für Captures ab n3092:

capture:
  identifier
  & identifier
  this

Der Text erwähnt nur Capture-by-Copy und Capture-by-Reference und erwähnt keinerlei Konstanz.

Fühlt sich für mich wie ein Versehen an, aber ich habe den Standardisierungsprozess nicht sehr genau verfolgt.

Steve M.
quelle
47
Ich habe gerade einen Fehler auf eine Variable zurückgeführt, die von der Erfassung geändert wurde, die veränderlich war, aber hätte sein sollen const. Oder genauer gesagt, wenn die Erfassungsvariable constwäre, hätte der Compiler dem Programmierer das richtige Verhalten aufgezwungen. Es wäre schön, wenn die Syntax unterstützt würde [&mutableVar, const &constVar].
Sean
Es scheint, dass dies mit C ++ 14 möglich sein sollte, aber ich kann es nicht zum Laufen bringen. Irgendwelche Vorschläge?
Aaron McDaid
37
Die Konstanz wird von der erfassten Variablen geerbt. Also , wenn Sie aufnehmen mögen , awie consterklären , const auto &b = a;bevor das Lambda - und Captureb
StenSoft
7
@StenSoft Bleargh. Außer anscheinend trifft dies nicht zu, wenn eine Mitgliedsvariable als Referenz erfasst wird: [&foo = this->foo]Innerhalb einer constFunktion wird eine Fehlermeldung angezeigt, die besagt, dass die Erfassung selbst Qualifizierer verwirft. Dies könnte jedoch ein Fehler in GCC 5.1 sein, nehme ich an.
Kyle Strand
119

Im mit static_cast/ const_cast:

[&best_string = static_cast<const std::string&>(best_string)](const string& s)
{
    best_string = s; // fails
};

DEMO


Im mit std::as_const:

[&best_string = std::as_const(best_string)](const string& s)
{
    best_string = s; // fails
};

DEMO 2

Piotr Skotnicki
quelle
Vielleicht sollte dies auch in die akzeptierte Antwort umgewandelt werden? In jedem Fall sollte es eine gute Antwort geben, die sowohl c ++ 11 als auch c ++ 14 abdeckt. Obwohl, ich denke, es könnte argumentiert werden, dass c ++ 14 in den kommenden Jahren für alle gut genug sein wird
Aaron McDaid
12
@AaronMcDaid const_castkann ein flüchtiges Objekt bedingungslos in ein const-Objekt ändern (wenn es zum Umwandeln aufgefordert wird const), um Einschränkungen hinzuzufügen, die ich bevorzugestatic_cast
Piotr Skotnicki
1
@PiotrSkotnicki auf der anderen Seite, static_castum Konstante Referenz kann stillschweigend eine temporäre erstellen, wenn Sie den Typ nicht genau richtig bekommen
MM
24
@ MM &basic_string = std::as_const(best_string)sollte alle Probleme lösen
Piotr Skotnicki
14
Außer das Problem der @PiotrSkotnicki , dass eine abscheuliche Art und Weise zu schreiben , etwas, das sollte als so einfach sein const& best_string.
Kyle Strand
12

Ich denke, der Erfassungsteil sollte nicht angeben const, da die Erfassung bedeutet, dass er nur eine Möglichkeit benötigt, auf die äußere Bereichsvariable zuzugreifen.

Der Bezeichner wird im äußeren Bereich besser angegeben.

const string better_string = "XXX";
[&better_string](string s) {
    better_string = s;    // error: read-only area.
}

Die Lambda-Funktion ist const (kann den Wert in ihrem Bereich nicht ändern). Wenn Sie also eine Variable nach Wert erfassen, kann die Variable nicht geändert werden, aber die Referenz befindet sich nicht im Lambda-Bereich.

zhb
quelle
1
@Amarnath Balasubramani: Es ist nur meine Meinung, ich denke, es ist nicht notwendig, eine Konstantenreferenz im Lambda-Erfassungsteil anzugeben. Warum sollte es hier eine Variable const geben und nicht an einer anderen Stelle const (wenn dies möglich ist, ist es fehleranfällig ). Ich freue mich trotzdem über Ihre Antwort.
zhb
2
Wenn Sie better_stringinnerhalb des enthaltenen Bereichs Änderungen vornehmen müssen, funktioniert diese Lösung nicht. Der Anwendungsfall für die Erfassung als const-ref ist, wenn die Variable im enthaltenen Bereich veränderbar sein muss, jedoch nicht im Lambda.
Jonathan Sharman
@ JonathanSharman, es kostet Sie nichts, eine konstante Referenz auf eine Variable zu erstellen, so dass Sie eine erstellen const string &c_better_string = better_string;und sie glücklich an das Lambda übergeben können:[&c_better_string]
Steed
@Steed Das Problem dabei ist, dass Sie einen zusätzlichen Variablennamen in den umgebenden Bereich einfügen. Ich denke, die obige Lösung von Piotr Skotnicki ist die sauberste, da sie Konstanzkorrektheit erreicht und gleichzeitig die variablen Bereiche minimal hält.
Jonathan Sharman
@ JonathanSharman, hier betreten wir das Land der Meinungen - was ist das Schönste oder Sauberste oder was auch immer. Mein Punkt ist, dass beide Lösungen für die Aufgabe geeignet sind.
Steed
8

Ich denke, wenn Sie die Variable nicht als Parameter des Funktors verwenden, sollten Sie die Zugriffsebene der aktuellen Funktion verwenden. Wenn Sie denken, Sie sollten nicht, dann trennen Sie Ihr Lambda von dieser Funktion, es ist nicht Teil davon.

Auf jeden Fall können Sie auf einfache Weise dasselbe erreichen, was Sie möchten, indem Sie stattdessen eine andere const-Referenz verwenden:

#include <cstdlib>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

int main()
{
    string strings[] = 
    {
        "hello",
        "world"
    };
    static const size_t num_strings = sizeof(strings)/sizeof(strings[0]);

    string best_string = "foo";
    const string& string_processed = best_string;

    for_each( &strings[0], &strings[num_strings], [&string_processed]  (const string& s)  -> void 
    {
        string_processed = s;    // this should fail
    }
    );
    return 0;
}

Dies entspricht jedoch der Annahme, dass Ihr Lambda von der aktuellen Funktion isoliert werden muss, sodass es kein Lambda ist.

Klaim
quelle
1
Die Erfassungsklausel erwähnt immer noch best_stringnur. Abgesehen davon "lehnt" GCC 4.5 den Code wie beabsichtigt erfolgreich ab.
Sellibitze
Ja, dies würde mir die Ergebnisse liefern, die ich auf technischer Ebene erzielen wollte. Letztendlich scheint die Antwort auf meine ursprüngliche Frage jedoch "Nein" zu sein.
John Dibling
Warum sollte das ein "Nicht-Lambda" sein?
Weil die Natur eines Lambda ist, dass es kontextabhängig ist. Wenn Sie keinen bestimmten Kontext benötigen, ist dies nur eine schnelle Möglichkeit, einen Funktor zu erstellen. Wenn der Funktor kontextunabhängig sein soll, machen Sie ihn zu einem echten Funktor.
Klaim
3
"Wenn der Funktor kontextunabhängig sein sollte, machen Sie ihn zu einem echten Funktor" ... und küssen Sie sich möglicherweise zum Abschied?
Andrew Lazarus
5

Ich denke, Sie haben drei verschiedene Möglichkeiten:

  • Verwenden Sie keine const-Referenz, sondern eine Kopienerfassung
  • ignorieren Sie die Tatsache, dass es modifizierbar ist
  • Verwenden Sie std :: bind, um ein Argument einer Binärfunktion zu binden, die eine const-Referenz hat.

mit einer Kopie

Das Interessante an Lambdas mit Copy Captures ist, dass diese tatsächlich schreibgeschützt sind und daher genau das tun, was Sie möchten.

int main() {
  int a = 5;
  [a](){ a = 7; }(); // Compiler error!
}

mit std :: bind

std::bindreduziert die Arität einer Funktion. Beachten Sie jedoch, dass dies über einen Funktionszeiger zu einem indirekten Funktionsaufruf führen kann / wird.

int main() {
  int a = 5;
  std::function<int ()> f2 = std::bind( [](const int &a){return a;}, a);
}
Alex
quelle
1
Außer Änderungen an der Variablen im enthaltenen Bereich werden nicht im Lambda berücksichtigt. Es ist keine Referenz, es ist nur eine Variable, die nicht neu zugewiesen werden sollte, da die Neuzuweisung nicht bedeuten würde, was sie zu bedeuten scheint.
Grault
4

Es gibt einen kürzeren Weg.

Beachten Sie, dass vor "best_string" kein kaufmännisches Und steht.

Es wird vom Typ "const std :: reference_wrapper << T >>" sein.

[best_string = cref(best_string)](const string& s)
{
    best_string = s; // fails
};

http://coliru.stacked-crooked.com/a/0e54d6f9441e6867

Sergey Palitsin
quelle
0

Verwenden Sie clang oder warten Sie, bis dieser gcc-Fehler behoben ist: Fehler 70385: Lambda-Erfassung durch Referenz der const-Referenz schlägt fehl [ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70385 ]

user1448926
quelle
1
Während dieser Link die Frage beantworten kann, ist es besser, die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen. Nur-Link-Antworten können ungültig werden, wenn sich die verknüpfte Seite ändert. “
Div
Ok, ich habe meine Antwort bearbeitet, um hier eine Beschreibung des gcc-Fehlers hinzuzufügen.
user1448926
Dies ist eine ziemlich indirekte Antwort auf die Frage, falls vorhanden. Der Fehler besteht darin, wie ein Compiler beim Erfassen einer Konstante fehlschlägt. Warum funktioniert eine Möglichkeit, das Problem in der Frage zu beheben oder zu umgehen, möglicherweise nicht mit gcc.
Stein
0

Wenn Sie eine Konstante verwenden, setzt der kaufmännische Und-Wert des Algorithmus die Zeichenfolge auf den ursprünglichen Wert. Mit anderen Worten, das Lambda definiert sich nicht wirklich als Parameter der Funktion, obwohl der umgebende Bereich eine zusätzliche Variable hat ... Ohne sie zu definieren Die Zeichenfolge wird jedoch nicht als typische [&, & best_string] (Zeichenfolgenkonstante) definiert. Daher ist es höchstwahrscheinlich besser, wenn wir sie einfach dabei belassen und versuchen, die Referenz zu erfassen.

Saith
quelle
Es ist eine sehr alte Frage: Ihrer Antwort fehlt der Kontext, auf den sich die C ++ - Version bezieht, auf die Sie sich beziehen. Bitte geben Sie diesen Inhalt an.
ZF007