Cattura lambda come riferimento const?


166

È possibile catturare per riferimento const in un'espressione lambda?

Voglio che il compito indicato di seguito fallisca, ad esempio:

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

Aggiornamento: poiché questa è una vecchia domanda, potrebbe essere utile aggiornarla se ci sono delle funzionalità in C ++ 14 per aiutarlo. Le estensioni in C ++ 14 ci consentono di catturare un oggetto non const per riferimento const? ( Agosto 2015 )


non dovrebbe il tuo look lambda come: [&, &best_string](string const s) { ...}?
Erjot,

3
acquisizione davvero incoerente. "const &" può essere molto utile quando si dispone di oggetti const di grandi dimensioni a cui è necessario accedere ma non modificati nella funzione lambda
sergtk

guardando il codice. potresti usare un lambda a due parametri e associare il secondo come riferimento const. viene fornito con un costo però.
Alex,

1
Ciò non è possibile in C ++ 11 sembrerebbe. Ma forse possiamo aggiornare questa domanda per C ++ 14 - ci sono estensioni che lo consentono? Il C ++ 14 cattura lambda generalizzata?
Aaron McDaid il

Risposte:


127

const non è nella grammatica per le acquisizioni a partire da n3092:

capture:
  identifier
  & identifier
  this

Il testo menziona solo l'acquisizione per copia e l'acquisizione per riferimento e non menziona alcun tipo di costanza.

Mi sembra una svista, ma non ho seguito da vicino il processo di standardizzazione.


47
Ho appena rintracciato un bug su una variabile modificata dall'acquisizione che era mutabile, ma avrebbe dovuto esserlo const. O più correttamente, se la variabile di acquisizione fosse const, il compilatore avrebbe applicato il comportamento corretto sul programmatore. Sarebbe bello se la sintassi fosse supportata [&mutableVar, const &constVar].
Sean

Sembra che questo dovrebbe essere possibile con C ++ 14, ma non riesco a farlo funzionare. Eventuali suggerimenti?
Aaron McDaid il

37
La costanza è ereditata dalla variabile acquisita. Quindi, se si desidera catturare acome const, dichiarare const auto &b = a;prima della lambda e catturareb
StenSoft

7
@StenSoft Bleargh. Tranne apparentemente ciò non si applica quando si acquisisce una variabile membro per riferimento: [&foo = this->foo]all'interno di una constfunzione mi viene indicato un errore che indica che l' acquisizione stessa scarta i qualificatori. Questo potrebbe essere un bug in GCC 5.1, suppongo.
Kyle Strand,

119

Nel usando static_cast/ const_cast:

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

DEMO


Nel utilizzando std::as_const:

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

DEMO 2


Inoltre, forse questo dovrebbe essere modificato nella risposta accettata? Ad ogni modo, dovrebbe esserci una buona risposta che copre sia c ++ 11 che c ++ 14. Anche se, immagino, si potrebbe sostenere che il c ++ 14 sarà abbastanza buono per tutti nei prossimi anni
Aaron McDaid,

12
@AaronMcDaid const_castpuò cambiare incondizionatamente un oggetto volatile in un oggetto const (quando viene chiesto di lanciarlo const), quindi, per aggiungere vincoli che preferiscostatic_cast
Piotr Skotnicki,

1
@PiotrSkotnicki, d'altra parte, il static_castriferimento costante può creare silenziosamente un temporaneo se non si ottiene il tipo esattamente giusto
MM

24
@MM &basic_string = std::as_const(best_string)dovrebbe risolvere tutti i problemi
Piotr Skotnicki,

14
@PiotrSkotnicki Tranne il problema di essere un modo orribile di scrivere qualcosa che dovrebbe essere semplice come const& best_string.
Kyle Strand,

12

Penso che la parte di acquisizione non debba specificare const, poiché la cattura significa, ha solo bisogno di un modo per accedere alla variabile dell'ambito esterno.

Lo specificatore è meglio specificato nell'ambito esterno.

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

La funzione lambda è const (non è possibile modificare il valore nel suo ambito), quindi quando si acquisisce una variabile in base al valore, la variabile non può essere modificata, ma il riferimento non si trova nell'ambito di lambda.


1
@Amarnath Balasubramani: È solo la mia opinione, penso che non sia necessario specificare un riferimento const nella parte di acquisizione lambda, perché dovrebbe esserci una variabile const qui e non const in un altro posto (se possibile, sarà soggetta a errori ). felice di vedere comunque la tua risposta.
zhb,

2
Se è necessario modificare better_stringnell'ambito di contenimento, questa soluzione non funzionerà. Il caso d'uso per la cattura come const-ref è quando la variabile deve essere mutabile nell'ambito di contenimento ma non all'interno della lambda.
Jonathan Sharman,

@JonathanSharman, non ti costa nulla creare un riferimento const a una variabile, quindi puoi fare un const string &c_better_string = better_string;e passarlo felicemente alla lambda:[&c_better_string]
Steed

@Steed Il problema è che stai introducendo un nome di variabile extra nell'ambito circostante. Penso che la soluzione di Piotr Skotnicki sopra sia la più pulita, in quanto raggiunge la correttezza costante mantenendo al minimo gli ambiti variabili.
Jonathan Sharman,

@JonathanSharman, qui entriamo nella terra delle opinioni: qual è la più bella, la più pulita o qualunque altra cosa. Il mio punto è che entrambe le soluzioni sono adatte al compito.
Steed

8

Immagino che se non stai usando la variabile come parametro del functor, allora dovresti usare il livello di accesso della funzione corrente. Se pensi che non dovresti, allora separa la tua lambda da questa funzione, non fa parte di essa.

Ad ogni modo, puoi facilmente ottenere la stessa cosa che desideri utilizzando invece un altro riferimento const:

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

Ma è lo stesso che supporre che il tuo lambda debba essere isolato dalla funzione corrente, rendendolo un non-lambda.


1
La clausola di acquisizione cita ancora best_stringsolo. A parte questo, GCC 4.5 "rifiuta con successo" il codice come previsto.
sellibitze,

Sì, questo mi darebbe i risultati che stavo cercando di ottenere a livello tecnico. Alla fine, tuttavia, la risposta alla mia domanda originale sembra essere "no".
John Dibling,

Perché dovrebbe renderlo un "non-lambda"?

Perché la natura di una lambda è che dipende dal contesto. Se non hai bisogno di un contesto specifico, allora è solo un modo rapido per creare un funzione. Se il funzione deve essere indipendente dal contesto, rendila una funzione reale.
Klaim,

3
"Se il functor deve essere indipendente dal contesto, rendilo un vero functor" ... e il bacio è possibile allinearti addio?
Andrew Lazarus,

5

Penso che tu abbia tre diverse opzioni:

  • non usare il riferimento const, ma usa una cattura della copia
  • ignora il fatto che è modificabile
  • usa std :: bind per associare un argomento di una funzione binaria che ha un riferimento const.

usando una copia

La parte interessante di lambdas con le acquisizioni di copie è che queste sono effettivamente di sola lettura e quindi fanno esattamente quello che vuoi che facciano.

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

usando std :: bind

std::bindriduce l'arità di una funzione. Si noti tuttavia che ciò potrebbe / porterà a una chiamata di funzione indiretta tramite un puntatore a funzione.

int main() {
  int a = 5;
  std::function<int ()> f2 = std::bind( [](const int &a){return a;}, a);
}

1
Tranne che le modifiche alla variabile nell'ambito di contenimento non si rifletteranno nella lambda. Non è un riferimento, è solo una variabile che non dovrebbe essere riassegnata perché la riassegnazione non significherebbe cosa significherebbe.
Grault,


0

Usa clang o aspetta fino a quando questo bug gcc è stato risolto: bug 70385: acquisizione lambda per riferimento del riferimento const non riuscita [ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70385 ]


1
Sebbene questo collegamento possa rispondere alla domanda, è meglio includere qui le parti essenziali della risposta e fornire il collegamento come riferimento. Le risposte di solo collegamento possono diventare non valide se la pagina collegata cambia. "
Div

Ok, ho modificato la mia risposta per aggiungere la descrizione del bug gcc qui.
user1448926

Questa è una risposta piuttosto indiretta alla domanda, se presente. Il bug riguarda il modo in cui un compilatore fallisce quando acquisisce qualcosa di const, quindi forse perché un modo di affrontare o aggirare il problema nella domanda potrebbe non funzionare con gcc.
Stein,

0

L'uso di una const farà semplicemente in modo che l'algoritmo e commerciale imposti la stringa sul suo valore originale, in altre parole, la lambda non si definirà realmente come parametro della funzione, anche se l'ambito circostante avrà una variabile extra ... Senza definirla tuttavia, non definirebbe la stringa come la tipica [&, & best_string] (string const s) Pertanto , è molto meglio se la lasciamo, cercando di catturare il riferimento.


È una domanda molto vecchia: la tua risposta non ha un contesto correlato alla versione C ++ a cui ti riferisci. Si prega di fornire questo contenuto.
ZF007,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.