Apa keuntungan menggunakan referensi penerusan dalam loop berbasis rentang?

115

const auto&sudah cukup jika saya ingin melakukan operasi hanya-baca. Namun, saya telah bertemu

for (auto&& e : v)  // v is non-const

beberapa kali baru-baru ini. Ini membuat saya bertanya-tanya:

Mungkinkah dalam beberapa kasus sudut yang tidak jelas ada beberapa manfaat kinerja dalam menggunakan referensi penerusan, dibandingkan dengan auto&atau const auto&?

( shared_ptradalah tersangka untuk kasus sudut yang tidak jelas)


Perbarui Dua contoh yang saya temukan di favorit saya:

Adakah kerugian menggunakan referensi const saat melakukan iterasi pada tipe dasar?
Dapatkah saya dengan mudah mengulang nilai peta menggunakan for loop berbasis rentang?

Harap konsentrasikan pada pertanyaan: mengapa saya ingin menggunakan auto && in range-based for loop?

Ali
sumber
4
Apakah Anda benar-benar melihatnya "sering"?
Balapan Ringan di Orbit
2
Saya tidak yakin ada cukup konteks dalam pertanyaan Anda bagi saya untuk mengukur seberapa "gila" di mana Anda melihatnya.
Balapan Ringan di Orbit
2
@LightnessRacesinOrbit Singkat cerita: mengapa saya ingin menggunakan auto&&loop berbasis jangkauan?
Ali

Jawaban:

94

Satu-satunya keuntungan yang bisa saya lihat adalah ketika iterator urutan mengembalikan referensi proxy dan Anda perlu mengoperasikan referensi itu dengan cara non-const. Misalnya pertimbangkan:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    for (auto& e : v)
        e = true;
}

Ini tidak dapat dikompilasi karena rvalue yang vector<bool>::referencedikembalikan dari iteratortidak akan mengikat ke referensi lvalue non-const. Tapi ini akan berhasil:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    for (auto&& e : v)
        e = true;
}

Semua yang dikatakan, saya tidak akan membuat kode dengan cara ini kecuali Anda tahu Anda perlu memenuhi kasus penggunaan seperti itu. Yaitu saya tidak akan melakukan ini cuma-cuma karena tidak menyebabkan orang bertanya-tanya apa yang Anda lakukan. Dan jika saya melakukannya, tidak ada salahnya untuk menyertakan komentar mengapa:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    // using auto&& so that I can handle the rvalue reference
    //   returned for the vector<bool> case
    for (auto&& e : v)
        e = true;
}

Sunting

Kasus terakhir saya ini harus benar-benar menjadi template yang masuk akal. Jika Anda tahu bahwa loop selalu menangani referensi proxy, maka itu autoakan berfungsi dengan baik auto&&. Tetapi ketika loop terkadang menangani referensi non-proxy dan terkadang referensi proxy, maka saya pikir auto&&akan menjadi solusi pilihan.

Howard Hinnant
sumber
3
Di sisi lain, tidak ada kerugian eksplisit , bukan? (Selain dari orang yang berpotensi membingungkan, yang menurut saya tidak terlalu layak untuk disebutkan, secara pribadi.)
ildjarn
7
Istilah lain untuk menulis kode yang tidak perlu membingungkan orang adalah: menulis kode yang membingungkan. Cara terbaik adalah membuat kode Anda sesederhana mungkin, tetapi tidak lebih sederhana. Ini akan membantu menjaga bug menghitung mundur. Meskipun begitu, saat && menjadi lebih familiar, maka mungkin 5 tahun dari sekarang orang akan mengharapkan idiom auto && (dengan asumsi itu sebenarnya tidak membahayakan). Saya tidak tahu apakah itu akan terjadi atau tidak. Tapi sederhana ada di mata yang melihatnya, dan jika Anda menulis lebih dari sekedar diri Anda sendiri, pertimbangkan pembaca Anda.
Howard Hinnant
7
Saya lebih suka const auto&ketika saya ingin kompiler membantu saya memeriksa bahwa saya tidak sengaja mengubah elemen dalam urutan.
Howard Hinnant
31
Saya pribadi suka menggunakan auto&&kode umum di mana saya perlu mengubah elemen urutan. Jika tidak, saya akan tetap berpegang pada auto const&.
Xeo
7
@Xeo: +1 Karena para peminat seperti Anda, terus bereksperimen dan mendorong cara yang lebih baik untuk melakukan sesuatu, C ++ terus berkembang. Terima kasih. :-)
Howard Hinnant
26

Menggunakan auto&&atau referensi universal dengan range-based for-loop memiliki keuntungan bahwa Anda menangkap apa yang Anda dapatkan. Untuk sebagian besar jenis iterator, Anda mungkin akan mendapatkan a T&atau a T const&untuk beberapa jenis T. Kasus yang menarik adalah di mana dereferensi sebuah iterator menghasilkan sementara: C ++ 2011 mendapatkan persyaratan yang lebih longgar dan iterator tidak selalu diperlukan untuk menghasilkan nilai l. Penggunaan referensi universal cocok dengan penerusan argumen di std::for_each():

template <typename InIt, typename F>
F std::for_each(InIt it, InIt end, F f) {
    for (; it != end; ++it) {
        f(*it); // <---------------------- here
    }
    return f;
}

Fungsi objek fdapat mengobati T&, T const&dan Tberbeda. Mengapa body dari range-based for-loop berbeda? Tentu saja, untuk benar-benar memanfaatkan hasil deduksi tipe menggunakan referensi universal, Anda harus meneruskannya sesuai:

for (auto&& x: range) {
    f(std::forward<decltype(x)>(x));
}

Tentu saja, menggunakan std::forward()cara yang Anda terima dari nilai yang dikembalikan untuk dipindahkan. Apakah objek seperti ini masuk akal dalam kode non-template, saya belum tahu (belum?). Saya dapat membayangkan bahwa menggunakan referensi universal dapat menawarkan lebih banyak informasi kepada kompiler untuk melakukan Hal yang Benar. Dalam kode templated, ia tidak membuat keputusan apa pun tentang apa yang harus terjadi dengan objek.

Dietmar Kühl
sumber
9

Saya hampir selalu menggunakan auto&&. Mengapa digigit oleh kasus tepi saat Anda tidak perlu? Ini juga lebih pendek untuk diketik, dan saya merasa lebih ... transparan. Saat Anda menggunakan auto&& x, maka Anda tahu xpersis *it, setiap saat.

Anak anjing
sumber
29
Masalah saya adalah Anda menyerah constdengan auto&&jika const auto&sudah cukup. Pertanyaannya menanyakan kasus sudut di mana saya bisa digigit. Apa kasus sudut yang belum disebutkan oleh Dietmar atau Howard?
Ali
2
Berikut adalah cara untuk mendapatkan digigit jika Anda melakukan penggunaan auto&&. Jika jenis yang Anda tangkap harus dipindahkan di badan perulangan (misalnya), dan diubah ke tanggal yang akan datang sehingga ditetapkan ke const&jenis, kode Anda akan terus berfungsi secara diam-diam, tetapi gerakan Anda akan menjadi salinan. Kode ini akan sangat menipu. Namun, jika Anda secara eksplisit menetapkan jenis sebagai referensi nilai-r, siapa pun yang mengubah jenis penampung akan mendapatkan kesalahan kompilasi karena Anda benar-benar ingin objek ini dipindahkan dan tidak disalin ...
cyberbisson
@ cyberbisson Saya baru saja menemukan ini: bagaimana saya bisa menegakkan referensi nilai r tanpa secara eksplisit menentukan jenisnya? Sesuatu seperti for (std::decay_t<decltype(*v.begin())>&& e: v)? Saya kira ada cara yang lebih baik ...
Jerry Ma
@JerryMa Itu tergantung pada apa yang Anda lakukan tahu tentang jenis. Seperti disebutkan di atas, auto&&akan memberikan "referensi universal", jadi apa pun selain itu akan memberi Anda tipe yang lebih spesifik. Jika vdiasumsikan a vector, Anda bisa melakukan decltype(v)::value_type&&, yang saya asumsikan Anda inginkan dengan mengambil hasil dari operator*pada tipe iterator. Anda juga bisa melakukan decltype(begin(v))::value_type&&untuk memeriksa jenis iterator sebagai ganti wadahnya. Namun, jika kita memiliki sedikit wawasan tentang jenisnya, kita mungkin menganggapnya sedikit lebih jelas untuk hanya menggunakan auto&&...
cyberbisson