Rentang tidak bersalah berdasarkan untuk loop tidak bekerja

11

Berikut ini tidak mengkompilasi:

#include <iostream>

int main()
{
    int a{},b{},c{},d{};

    for (auto& s : {a, b, c, d}) {
        s = 1;
    }
    std::cout << a << std::endl;
    return 0;
}

Cobalah di godbolt

Kesalahan kompiler adalah: error: assignment of read-only reference 's'

Sekarang dalam kasus saya yang sebenarnya daftar dibuat dari variabel anggota di kelas.

Sekarang, ini tidak berfungsi karena ekspresi menjadi initializer_list<int>yang benar-benar menyalin a, b, c, dan d - karenanya juga tidak memungkinkan modifikasi.

Pertanyaan saya ada dua:

Apakah ada motivasi di balik tidak memungkinkan untuk menulis rentang berbasis untuk loop dengan cara ini? misalnya. mungkin ada kasus khusus untuk ekspresi penyangga telanjang.

Apa cara rapi dan rapi untuk memperbaiki jenis loop ini?

Sesuatu di sepanjang garis ini akan lebih disukai:

for (auto& s : something(a, b, c, d)) {
    s = 1;
}

Saya tidak menganggap pointer tipuan solusi yang baik (yaitu {&a, &b, &c, &d}) - solusi apa pun harus memberikan referensi elemen secara langsung ketika iterator dide-referensi .

sayang
sumber
1
Sebuah solusi sederhana (bahwa saya tidak akan benar-benar menggunakan sendiri) adalah untuk membuat daftar pointer sebagai gantinya: { &a, &b, &c, &d }.
Beberapa programmer dude
2
initializer_listsebagian besar tampilan pada constarray.
Jarod42
Apa yang mungkin akan saya lakukan adalah menginisialisasi variabel secara eksplisit, satu per satu. Ini tidak akan menjadi jauh lebih banyak untuk ditulis, itu jelas dan eksplisit, dan ia melakukan apa yang dimaksudkan. :)
Beberapa programmer dude
3
jika Anda tidak mau { &a, &b, &c, &d }, Anda tidak akan menginginkan keduanya:for (auto& s : std::initializer_list<std::reference_wrapper<int>>{a, b, c, d}) { s.get() = 1; }
Jarod42
Pertanyaan "mengapa ini tidak bisa bekerja" adalah pertanyaan yang sangat berbeda dari "apa yang bisa saya lakukan untuk membuat sesuatu seperti ini berhasil?"
Nicol Bolas

Jawaban:

4

Kisaran tidak sesihir yang orang inginkan. Pada akhirnya, harus ada objek yang dapat dikompilasi oleh kompiler ke fungsi anggota atau fungsi bebas begin()dan end().

Yang terdekat Anda mungkin bisa datang adalah:

#include <iostream>

int main()
{
    int a{},b{},c{},d{};

    for (auto s : {&a, &b, &c, &d} ) {
        *s = 1;
    }
    std::cout << a << "\n";
    return 0;
}
mhhollomon
sumber
1
Anda bisa jatuh std::vector<int*>.
Jarod42
@ mhhollomon I secara eksplisit menyatakan saya tidak tertarik dengan solusi tipuan pointer.
darune
1
Seharusnya auto satau auto* stidak auto& s.
LF
@ Darune - Saya akan senang memiliki seseorang memberikan jawaban yang berbeda. Tidak jelas apakah jawaban seperti itu ada dengan standar saat ini.
mhhollomon
@ LF - setuju.
mhhollomon
4

Hanya solusi lain dalam ide pembungkus:

template<typename T, std::size_t size>
class Ref_array {
    using Array = std::array<T*, size>;

    class Iterator {
    public:
        explicit Iterator(typename Array::iterator it) : it_(it) {}

        void operator++() { ++it_; }
        bool operator!=(const Iterator& other) const { return it_ != other.it_; }
        decltype(auto) operator*() const { return **it_; }

    private:
        typename Array::iterator it_;
    };

public:
    explicit Ref_array(Array args) : args_(args) {}

    auto begin() { return Iterator(args_.begin()); }
    auto end() { return Iterator(args_.end()); }

private:
    Array args_;
};

template<typename T, typename... Ts>
auto something(T& first, Ts&... rest) {
    static_assert((std::is_same_v<T, Ts> && ...));
    return Ref_array<T, 1 + sizeof...(Ts)>({&first, &rest...});
}

Kemudian:

int main() {
    int a{}, b{}, c{}, d{};

    for (auto& s : something(a, b, c, d)) {
        std::cout << s;
        s = 1;
    }

    std::cout  << std::endl;
    for (auto& s : something(a, b, c, d))
        std::cout << s;
}

output

0000
1111
Evg
sumber
2
Ini adalah solusi / solusi yang layak dan baik. Ini ide yang mirip dengan jawaban saya sendiri (saya menggunakan std :: reference_wrapper dan tidak menggunakan template variadic)
darune
4

Menurut standar §11.6.4 Daftar-inisialisasi / p5 [dcl.init.list] [ Tambang Penekanan ]:

Objek tipe 'std :: initializer_list' dibangun dari daftar initializer seolah-olah implementasi dihasilkan dan terwujud (7.4) nilai awal dari tipe “array N const E” , di mana N adalah jumlah elemen dalam daftar initializer. Setiap elemen array itu diinisialisasi-salin dengan elemen yang sesuai dari daftar initializer, dan objek std :: initializer_list dibangun untuk merujuk ke array itu. [Catatan: Fungsi konstruktor atau konversi yang dipilih untuk salinan harus dapat diakses (Klausul 14) dalam konteks daftar penginisialisasi. - catatan akhir] Jika konversi penyempitan diperlukan untuk menginisialisasi salah satu elemen, program ini salah bentuk.

Dengan demikian, kompiler Anda mengeluh secara sah (yaitu, auto &sdikurangkan int const& sdan Anda tidak dapat menetapkannya sdalam rentang untuk loop).

Anda bisa mengatasi masalah ini dengan memperkenalkan wadah alih-alih daftar penginisialisasi (mis. `Std :: vector ') dengan' std :: reference_wrapper ':

#include <iostream>
#include <vector>
#include <functional>

int main()
{
    int a{},b{},c{},d{};

    for (auto& s : std::vector<std::reference_wrapper<int>>{a, b, c, d}) {
        s.get()= 1;
    }
    std::cout << a << std::endl;
    return 0;
}

Demo Langsung

101010
sumber
@ Jarod42 Ouups maaf, mengubah itu.
101010
Solusi Anda tidak cocok dengan kriteria saya untuk solusi yang bagus - jika saya senang dengan itu saya tidak akan meminta di tempat pertama :)
darune
juga kutipan Anda tidak berusaha menjawab pertanyaan saya
darune
1
@ Darune - sebenarnya, kutipan adalah alasan Anda for (auto& s : {a, b, c, d})tidak bekerja. Mengenai mengapa standar memiliki klausa itu ..... Anda harus bertanya kepada anggota komite standardisasi. Seperti banyak hal lain, alasannya bisa antara "Kami tidak menganggap kasus khusus Anda cukup berguna untuk diganggu" hingga "Terlalu banyak bagian standar yang harus diubah untuk mendukung kasus Anda, dan kami menunda pertimbangan semua itu sampai kita mengembangkan standar masa depan ".
Peter
Tidak bisakah kamu menggunakan saja std::array<std::reference_wrapper>>?
Toby Speight
1

Untuk memenuhi sintaks itu

for (auto& s : something{a, b, c, d}) {
    s = 1;
}

Anda dapat membuat pembungkus:

template <typename T>
struct MyRefWrapper
{
public:
    MyRefWrapper(T& p)  : p(&p) {}

    T& operator =(const T& value) const { return *p = value; }

    operator T& () const { return *p; }
private:
    T* p;     
};

Demo

Jarod42
sumber
1
Bagaimana itu berbeda dari std::reference_wrapper?
Toby Speight
1
@TobySpeight: std::reference_wrapperakan membutuhkan s.get() = 1;.
Jarod42
0

Solusi: gunakan pembungkus referensi

template <class It>
struct range_view_iterator : public It{//TODO: don't inherit It
    auto& operator*() {
        return (*this)->get();
    }
};

template<class It>
range_view_iterator(It) -> range_view_iterator<It>;


template<class T>
struct range_view {
    std::vector<std::reference_wrapper<T> > refs_;
    range_view(std::initializer_list<std::reference_wrapper<T> > refs) : refs_{refs} {
    }

    auto begin() {
        return range_view_iterator{ refs_.begin() };
    }

    auto end() {
        return range_view_iterator{ refs_.end() };
    }
};

Kemudian digunakan sebagai:

for (auto& e : range_view<int>{a, b, c, d}) {
    e = 1;
}

Ini tidak mencoba menjawab pertanyaan pertama.

sayang
sumber
-1

Anda bisa membuat kelas pembungkus untuk menyimpan referensi dan yang akan memiliki operator penugasan untuk memperbarui nilai ini:

template<class T>
struct Wrapper {
    T& ref;

    Wrapper(T& ref)
    : ref(ref){}

    template<class U>
    void operator=(U u) {
        ref = u;
    }
};

template<class...T>
auto sth(T&...t) {
    return std::array< Wrapper<std::common_type_t<T...> > ,sizeof...(t) >{Wrapper(t)...};
};

int main(){
    int a{},b{},c{},d{};

    for (auto s : sth(a,b,c,d)) {
        s = 1;
    }
    std::cout << a << std::endl; // 1

Demo langsung

rafix07
sumber