Tangkapan Lambda sebagai referensi const?

166

Apakah mungkin untuk menangkap dengan referensi const dalam ekspresi lambda?

Saya ingin tugas yang ditandai di bawah gagal, misalnya:

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

Pembaruan: Karena ini adalah pertanyaan lama, mungkin baik untuk memperbaruinya jika ada fasilitas di C ++ 14 untuk membantu dengan ini. Apakah ekstensi dalam C ++ 14 memungkinkan kita untuk menangkap objek non-const dengan referensi const? ( Agustus 2015 )

John Dibling
sumber
harus tidak terlihat lambda Anda seperti: [&, &best_string](string const s) { ...}?
erjot
3
penangkapan yang benar-benar tidak konsisten. "const &" bisa sangat berguna ketika Anda memiliki objek const besar yang harus diakses tetapi tidak dimodifikasi dalam fungsi lambda
sergtk
melihat kodenya. Anda dapat menggunakan lambda dua parameter dan mengikat yang kedua sebagai referensi const. hadir dengan biaya sekalipun.
Alex
1
Ini tidak mungkin di C ++ 11 sepertinya. Tetapi mungkin kita dapat memperbarui pertanyaan ini untuk C ++ 14 - apakah ada ekstensi yang memungkinkan ini? C ++ 14 menangkap lambda umum?
Aaron McDaid

Jawaban:

127

const tidak ada dalam tata bahasa untuk menangkap pada n3092:

capture:
  identifier
  & identifier
  this

Teks hanya menyebutkan capture-by-copy dan capture-by-reference dan tidak menyebutkan jenis ketegasan.

Rasanya seperti kekeliruan bagi saya, tetapi saya belum mengikuti proses standardisasi dengan sangat cermat.

Steve M
sumber
47
Saya baru saja melacak bug kembali ke variabel yang dimodifikasi dari tangkapan yang bisa berubah, tetapi seharusnya const. Atau lebih tepatnya, jika variabel capture adalah const, kompiler akan memberlakukan perilaku yang benar pada programmer. Alangkah baiknya jika sintaks didukung [&mutableVar, const &constVar].
Sean
Sepertinya ini seharusnya dimungkinkan dengan C ++ 14, tapi saya tidak bisa membuatnya berfungsi. Ada saran?
Aaron McDaid
37
Constness diwarisi dari variabel yang ditangkap. Jadi jika Anda ingin menangkap asebagai const, nyatakan di const auto &b = a;depan lambda dan tangkapb
StenSoft
7
@StenSoft Bleargh. Kecuali ternyata ini tidak berlaku ketika menangkap variabel anggota dengan referensi: [&foo = this->foo]di dalam suatu constfungsi memberi saya kesalahan yang menyatakan bahwa tangkapan itu sendiri membuang kualifikasi. Ini bisa jadi bug di GCC 5.1, saya kira.
Kyle Strand
119

Di menggunakan static_cast/ const_cast:

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

DEMO


Di menggunakan std::as_const:

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

DEMO 2

Piotr Skotnicki
sumber
Juga, mungkin ini harus diedit menjadi jawaban yang diterima? Either way, harus ada satu jawaban bagus yang mencakup c ++ 11 dan c ++ 14. Meskipun, saya kira itu bisa diperdebatkan bahwa c ++ 14 akan cukup baik untuk semua orang selama tahun-tahun mendatang
Aaron McDaid
12
@AaronMcDaid const_castdapat tanpa syarat mengubah objek volatil ke objek const (ketika diminta untuk melakukan cast const), dengan demikian, untuk menambahkan kendala yang saya sukaistatic_cast
Piotr Skotnicki
1
@PiotrSkotnicki di sisi lain, static_castuntuk referensi const dapat secara diam-diam membuat sementara jika Anda tidak mendapatkan tipe yang tepat
MM
24
@ MM &basic_string = std::as_const(best_string)harus menyelesaikan semua masalah
Piotr Skotnicki
14
@PiotrSkotnicki Kecuali masalah itu menjadi cara yang mengerikan untuk menulis sesuatu yang seharusnya sesederhana const& best_string.
Kyle Strand
12

Saya pikir bagian penangkapan seharusnya tidak menentukan const, karena artinya penangkapan, itu hanya perlu cara untuk mengakses variabel lingkup luar.

Specifier lebih baik ditentukan dalam lingkup luar.

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

fungsi lambda adalah const (tidak dapat mengubah nilai dalam cakupannya), jadi ketika Anda menangkap variabel dengan nilai, variabel tidak dapat diubah, tetapi referensi tidak dalam cakupan lambda.

zhb
sumber
1
@Amarnath Balasubramani: Itu hanya pendapat saya, saya pikir tidak perlu menentukan referensi const di bagian penangkapan lambda, mengapa harus ada variabel const di sini dan bukan const di tempat lain (jika mungkin, itu akan rentan kesalahan) ). senang melihat respons Anda.
zhb
2
Jika Anda perlu memodifikasi better_stringdalam lingkup yang berisi, maka solusi ini tidak akan berfungsi. Kasus penggunaan untuk menangkap sebagai const-ref adalah ketika variabel harus bisa berubah dalam lingkup yang berisi tetapi tidak di dalam lambda.
Jonathan Sharman
@ JonathanSharman, Anda tidak perlu mengeluarkan biaya apa pun untuk membuat referensi const ke variabel, sehingga Anda dapat membuat const string &c_better_string = better_string;dan dengan senang hati mengirimkannya ke lambda:[&c_better_string]
Steed
@Steed Masalah dengan itu adalah Anda memperkenalkan nama variabel tambahan ke dalam lingkup sekitarnya. Saya pikir solusi Piotr Skotnicki di atas adalah yang paling bersih, karena mencapai const-benar sambil menjaga ruang lingkup variabel minimal.
Jonathan Sharman
@ JonathanSharman, di sini kita memasuki tanah pendapat - apa yang tercantik, atau terbersih, atau apa pun. Maksud saya adalah bahwa kedua solusi cocok untuk tugas tersebut.
Steed
8

Saya kira jika Anda tidak menggunakan variabel sebagai parameter dari functor, maka Anda harus menggunakan level akses dari fungsi saat ini. Jika Anda merasa tidak seharusnya, maka pisahkan lambda Anda dari fungsi ini, itu bukan bagian darinya.

Bagaimanapun, Anda dapat dengan mudah mencapai hal yang sama yang Anda inginkan dengan menggunakan referensi const lain:

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

Tapi itu sama dengan mengasumsikan bahwa lambda Anda harus diisolasi dari fungsi saat ini, menjadikannya non-lambda.

Klaim
sumber
1
Klausa penangkapan masih menyebutkan best_stringhanya. Selain itu, GCC 4.5 "berhasil menolak" kode yang dimaksud.
sellibitze
Ya, ini akan memberi saya hasil yang saya coba capai pada tingkat teknis. Namun pada akhirnya, jawaban untuk pertanyaan awal saya tampaknya "tidak."
John Dibling
Mengapa hal itu menjadikannya "non-lambda"?
Karena sifat lambda adalah bahwa itu tergantung pada konteks. Jika Anda tidak memerlukan konteks tertentu maka itu hanya cara cepat untuk membuat functor. Jika functor harus independen konteks, jadikan itu functor nyata.
Klaim
3
"Jika functor harus independen terhadap konteks, jadikan itu functor nyata" ... dan ciuman yang memungkinkan untuk mengucapkan selamat tinggal?
Andrew Lazarus
5

Saya pikir Anda memiliki tiga opsi berbeda:

  • jangan gunakan referensi const, tetapi gunakan capture copy
  • abaikan fakta bahwa itu dapat dimodifikasi
  • gunakan std :: bind untuk mengikat satu argumen dari fungsi biner yang memiliki referensi const.

menggunakan salinan

Bagian yang menarik tentang lambda dengan salinan salinan adalah bahwa itu sebenarnya hanya dibaca dan karena itu lakukan persis apa yang Anda inginkan.

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

menggunakan std :: bind

std::bindmengurangi arity dari suatu fungsi. Namun perlu dicatat bahwa ini mungkin / akan menyebabkan panggilan fungsi tidak langsung melalui pointer fungsi.

int main() {
  int a = 5;
  std::function<int ()> f2 = std::bind( [](const int &a){return a;}, a);
}
Alex
sumber
1
Kecuali perubahan pada variabel dalam cakupan yang mengandung tidak akan tercermin dalam lambda. Ini bukan referensi, itu hanya variabel yang tidak boleh dipindahkan karena penugasan ulang tidak berarti apa yang tampaknya berarti.
Grault
4

Ada cara yang lebih pendek.

Perhatikan bahwa tidak ada tanda dan sebelum "best_string".

Ini akan menjadi tipe "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
sumber
0

Gunakan dentang atau tunggu sampai bug gcc ini diperbaiki: bug 70385: Lambda capture dengan referensi referensi const gagal [ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70385 ]

pengguna1448926
sumber
1
Meskipun tautan ini dapat menjawab pertanyaan, lebih baik untuk memasukkan bagian-bagian penting dari jawaban di sini dan memberikan tautan untuk referensi. Jawaban hanya tautan dapat menjadi tidak valid jika halaman tertaut berubah. "
Div
Oke, saya mengedit jawaban saya untuk menambahkan deskripsi bug gcc di sini.
user1448926
Ini merupakan jawaban tidak langsung dari pertanyaan, jika ada. Bugnya adalah tentang bagaimana kompiler gagal ketika menangkap sesuatu const, jadi mungkin mengapa beberapa cara untuk mengatasi atau mengatasi masalah dalam pertanyaan mungkin tidak bekerja dengan gcc.
Stein
0

Menggunakan const hanya akan memiliki algoritma ampersand mengatur string ke nilai aslinya, Dengan kata lain, lambda tidak akan benar-benar mendefinisikan dirinya sebagai parameter fungsi, meskipun lingkup sekitarnya akan memiliki variabel tambahan ... Tanpa mendefinisikannya meskipun, itu tidak akan mendefinisikan string sebagai khas [&, & best_string] (string const s) Oleh karena itu , kemungkinan besar lebih baik jika kita membiarkannya, mencoba menangkap referensi.

Saith
sumber
Ini pertanyaan yang sangat lama: jawaban Anda kurang konteks terkait dengan versi C ++ yang Anda maksud. Harap berikan konten ini.
ZF007