Przechwytywanie lambda jako odniesienie do stałej?

166

Czy możliwe jest przechwycenie przez odwołanie do stałej w wyrażeniu lambda?

Chcę, aby zadanie zaznaczone poniżej zakończyło się niepowodzeniem, na przykład:

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

Aktualizacja: ponieważ jest to stare pytanie, warto je zaktualizować, jeśli w C ++ 14 są narzędzia, które mogą w tym pomóc. Czy rozszerzenia w C ++ 14 pozwalają nam przechwytywać obiekt inny niż const za pomocą odwołania do stałej? ( Sierpień 2015 )

John Dibling
źródło
czy twoja lambda nie powinna wyglądać tak [&, &best_string](string const s) { ...}:?
erjot
3
naprawdę niespójne przechwytywanie. "const &" może być bardzo przydatne, gdy masz duży obiekt const, do którego należy uzyskać dostęp, ale nie modyfikować go w funkcji lambda
sergtk
patrząc na kod. możesz użyć dwuparametrowej lambda i powiązać drugą jako odniesienie do stałej. ma jednak swoją cenę.
Alex,
1
Wydaje się, że nie jest to możliwe w C ++ 11. Ale może możemy zaktualizować to pytanie dla C ++ 14 - czy są rozszerzenia, które na to pozwalają? Uogólniona lambda C ++ 14 przechwytuje?
Aaron McDaid

Odpowiedzi:

127

const nie ma w gramatyce przechwytywania od n3092:

capture:
  identifier
  & identifier
  this

Tekst wspomina tylko o przechwytywaniu przez kopię i przechwytywaniu przez odniesienie i nie wspomina o żadnym rodzaju trwałości.

Wydaje mi się, że to przeoczenie, ale nie śledziłem zbyt dokładnie procesu normalizacji.

Steve M.
źródło
47
Właśnie wyśledziłem błąd z powrotem do zmiennej modyfikowanej z przechwytywania, która była zmienna, ale powinna była const. Albo bardziej poprawnie, gdyby zmienną przechwytywania była const, kompilator wymusiłby prawidłowe zachowanie programisty. Byłoby miło, gdyby składnia była obsługiwana [&mutableVar, const &constVar].
Sean,
Wygląda na to, że powinno to być możliwe w C ++ 14, ale nie mogę go uruchomić. Jakieś sugestie?
Aaron McDaid
37
Stałość jest dziedziczona z przechwyconej zmiennej. Więc jeśli chcesz uchwycić ajako const, zadeklaruj const auto &b = a;przed b
lambdą
7
@StenSoft Bleargh. Z wyjątkiem tego, że najwyraźniej nie ma to zastosowania podczas przechwytywania zmiennej składowej przez odwołanie: [&foo = this->foo]wewnątrz constfunkcji pojawia się błąd informujący, że przechwytywanie samo odrzuca kwalifikatory. Przypuszczam, że może to być błąd w GCC 5.1.
Kyle Strand
119

W używając static_cast/ const_cast:

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

PRÓBNY


W używając std::as_const:

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

DEMO 2

Piotr Skotnicki
źródło
A może powinno to zostać dodane do zaakceptowanej odpowiedzi? Tak czy inaczej, powinna istnieć jedna dobra odpowiedź, która obejmuje zarówno c ++ 11, jak i c ++ 14. Chociaż, wydaje mi się, że można argumentować, że c ++ 14 będzie wystarczająco dobry dla wszystkich w nadchodzących latach
Aaron McDaid
12
@AaronMcDaid const_castmoże bezwarunkowo zmienić zmienny obiekt na obiekt const (gdy zostanie poproszony o rzutowanie const), a więc do dodawania ograniczeń, które preferujęstatic_cast
Piotr Skotnicki
1
Z drugiej strony @PiotrSkotnicki, static_castodwołanie do const może po cichu stworzyć tymczasowy, jeśli nie masz dokładnie tego typu
MM
24
@MM &basic_string = std::as_const(best_string)powinien rozwiązać wszystkie problemy
Piotr Skotnicki
14
@PiotrSkotnicki Poza tym, że jest to ohydny sposób na napisanie czegoś, co powinno być tak proste jak const& best_string.
Kyle Strand
12

Myślę, że część przechwytywania nie powinna określać const, ponieważ przechwytywanie oznacza, że ​​potrzebuje tylko sposobu na dostęp do zmiennej zakresu zewnętrznego.

Specyfikator jest lepiej określony w zakresie zewnętrznym.

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

Funkcja lambda jest stała (nie można zmienić wartości w swoim zakresie), więc kiedy przechwytujesz zmienną według wartości, zmienna nie może zostać zmieniona, ale odwołanie nie znajduje się w zakresie lambda.

zhb
źródło
1
@Amarnath Balasubramani: To tylko moja opinia, myślę, że nie ma potrzeby określania odniesienia do stałej w części przechwytywania lambda, dlaczego tutaj ma być zmienna stała, a nie stała w innym miejscu (jeśli to możliwe, będzie podatna na błędy ). i tak miło widzę twoją odpowiedź.
zhb
2
Jeśli musisz zmodyfikować better_stringw zakresie zawierającym, to rozwiązanie nie zadziała. Przykład użycia do przechwytywania jako const-ref ma miejsce, gdy zmienna musi być modyfikowalna w zakresie zawierającym, ale nie w zakresie lambda.
Jonathan Sharman
@JonathanSharman, nic Cię nie kosztuje, aby utworzyć stałą referencję do zmiennej, więc możesz zrobić const string &c_better_string = better_string;i szczęśliwie przekazać ją do lambdy:[&c_better_string]
Steed
@Steed Problem polega na tym, że wprowadzasz dodatkową nazwę zmiennej do otaczającego zakresu. Myślę, że powyższe rozwiązanie Piotra Skotnickiego jest najczystsze, ponieważ osiąga stałą poprawność przy zachowaniu minimalnych zakresów zmiennych.
Jonathan Sharman
@JonathanSharman, tu wkraczamy w krainę opinii - co jest najpiękniejsze, czy najczystsze, czy cokolwiek. Chodzi mi o to, że oba rozwiązania są odpowiednie do tego zadania.
Steed
8

Wydaje mi się, że jeśli nie używasz zmiennej jako parametru funktora, powinieneś użyć poziomu dostępu bieżącej funkcji. Jeśli uważasz, że nie powinieneś, oddziel swoją lambdę od tej funkcji, nie jest jej częścią.

W każdym razie możesz łatwo osiągnąć to samo, co chcesz, używając zamiast tego innego odwołania do stałej:

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

Ale to to samo, co założenie, że twoja lambda musi być odizolowana od bieżącej funkcji, co czyni ją inną niż lambda.

Klaim
źródło
1
Klauzula przechwytywania nadal wspomina best_stringtylko. Poza tym GCC 4.5 „skutecznie odrzuca” kod zgodnie z zamierzeniami.
sellibitze
Tak, to dałoby mi wyniki, które próbowałem osiągnąć na poziomie technicznym. Ostatecznie jednak odpowiedź na moje pierwotne pytanie wydaje się brzmieć „nie”.
John Dibling
Dlaczego miałoby to oznaczać „nie-lambdę”?
Ponieważ naturą lambda jest zależność od kontekstu. Jeśli nie potrzebujesz konkretnego kontekstu, to po prostu szybki sposób na stworzenie funktora. Jeśli funktor ma być niezależny od kontekstu, uczyń go prawdziwym funktorem.
Klaim
3
„Jeśli funktor ma być niezależny od kontekstu, uczyń go prawdziwym funktorem”… i możliwe całowanie na pożegnanie?
Andrew Lazarus
5

Myślę, że masz trzy różne opcje:

  • nie używaj odwołania do const, ale użyj przechwytywania kopii
  • zignoruj ​​fakt, że można go modyfikować
  • użyj std :: bind, aby powiązać jeden argument funkcji binarnej, która ma odwołanie do stałej.

za pomocą kopii

Ciekawą częścią lambd z przechwytywaniem kopii jest to, że są one w rzeczywistości tylko do odczytu i dlatego robią dokładnie to, czego chcesz.

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

używając std :: bind

std::bindzmniejsza liczbę funkcji. Należy jednak zauważyć, że może to / doprowadzi do pośredniego wywołania funkcji przez wskaźnik funkcji.

int main() {
  int a = 5;
  std::function<int ()> f2 = std::bind( [](const int &a){return a;}, a);
}
Alex
źródło
1
Z wyjątkiem zmian w zmiennej w zakresie zawierającym nie zostaną odzwierciedlone w lambdzie. To nie jest odniesienie, to tylko zmienna, której nie należy ponownie przypisywać, ponieważ ponowne przypisanie nie oznaczałoby tego, co mogłoby się wydawać oznaczać.
Grault
4

Jest krótszy sposób.

Zauważ, że przed „best_string” nie ma znaku ampersand.

Będzie to typ „const std :: reference_wrapper << T >>”.

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

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

Sergey Palitsin
źródło
0

Użyj clang lub poczekaj, aż ten błąd gcc zostanie naprawiony: błąd 70385: przechwytywanie Lambdy przez odniesienie do const nie powiedzie się [ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70385 ]

user1448926
źródło
1
Chociaż ten link może odpowiedzieć na pytanie, lepiej jest zawrzeć tutaj zasadnicze części odpowiedzi i podać link do odniesienia. Odpowiedzi zawierające tylko łącze mogą stać się nieprawidłowe, jeśli połączona strona ulegnie zmianie ”.
Div
Ok, zredagowałem moją odpowiedź, aby dodać tutaj opis błędu gcc.
user1448926
To jest dość pośrednia odpowiedź na pytanie, jeśli takie istnieje. Błąd polega na tym, że kompilator zawodzi podczas przechwytywania czegoś const, więc może dlaczego jakiś sposób rozwiązania lub obejścia problemu w pytaniu może nie działać z gcc.
Stein
0

Użycie stałej spowoduje po prostu, że algorytm ampersand ustawi ciąg na jego oryginalną wartość, innymi słowy, lambda nie będzie tak naprawdę definiować siebie jako parametru funkcji, chociaż otaczający zakres będzie miał dodatkową zmienną ... Bez jej definiowania chociaż nie zdefiniowałaby ciągu jako typowego [&, & best_string] (string const s) Dlatego najprawdopodobniej lepiej będzie, jeśli po prostu zostawimy go na tym, próbując przechwycić odniesienie.

Saith
źródło
To bardzo stare pytanie: w Twojej odpowiedzi brakuje kontekstu związanego z wersją C ++, do której się odnosisz. Proszę podać tę treść.
ZF007