Döngü çalışmayan masum aralık


11

Aşağıdaki gelmez değil derlemek:

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

Godbolt üzerinde dene

Derleyici hatası: error: assignment of read-only reference 's'

Şimdi benim gerçek durumumda liste bir sınıftaki üye değişkenlerden oluşur.

Şimdi, bu işe yaramıyor çünkü ifade initializer_list<int>aslında a, b, c ve d'yi kopyalayan bir ifadeye dönüşüyor - dolayısıyla değiştirmeye izin vermiyor.

Sorum iki katlıdır:

Bu şekilde döngü için bir menzil tabanlı yazmaya izin vermemenin arkasında herhangi bir motivasyon var mı? Örneğin. belki de çıplak ayraç ifadeleri için özel bir durum olabilir.

Bu tür bir döngüyü düzeltmenin sözdizimsel olarak düzgün bir yolu nedir ?

Bu hat boyunca bir şey tercih edilir:

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

Ben işaretçi dolaylama iyi bir çözüm (yani {&a, &b, &c, &d}) düşünmüyorum - herhangi bir çözüm yineleyici de başvurulan olduğunda doğrudan öğe referans vermelidir .


1
Basit bir geçici çözüm (gerçekten kendimi kullanmak olmaz) yerine işaretçiler bir listesini oluşturmaktır: { &a, &b, &c, &d }.
Bazı programcı ahbap

2
initializer_listçoğunlukla constdizide bir görünümdür .
Jarod42

Muhtemelen yapacağım şey değişkenleri tek tek başlatmaktır. Yazmak daha fazla olmayacak, açık ve açık ve amaçlananı yapıyor. :)
Bazı programcı dostum

3
istemiyorsanız { &a, &b, &c, &d }, hiçbirini istemezsiniz:for (auto& s : std::initializer_list<std::reference_wrapper<int>>{a, b, c, d}) { s.get() = 1; }
Jarod42

"Bu neden işe yaramaz" sorusu "bu işe benzer bir şey yapmak için ne yapabilirim?" Sorusundan çok farklı bir soru.
Nicol Bolas

Yanıtlar:


4

Sıradağlar insanların istediği kadar sihir değildir. Sonunda, derleyici bir üye işlev ya da serbest fonksiyonunda için çağrı oluşturabilir bir nesnenin olmalıdır begin()ve end().

Muhtemelen gelebileceğiniz en yakın şey:

#include <iostream>

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

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

1
Bırakabilirsiniz std::vector<int*>.
Jarod42

@mhhollomon Açıkça bir işaretçi dolaylı çözümle ilgilenmediğimi söyledim.
darune

1
Olmalı auto sya da auto* solmamalı auto& s.
LF

@darune - Birisine farklı bir cevap vermekten mutluluk duyarım. Mevcut standartta böyle bir cevabın olduğu açık değildir.
mhhollomon

@LF - kabul etti.
mhhollomon

4

Bir sarmalayıcı fikri içinde başka bir çözüm:

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...});
}

Sonra:

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

çıktılar

0000
1111

2
Bu iyi ve iyi bir çözüm / geçici çözümdür. Kendi
cevabımla

4

§11.6.4 Liste başlatma / p5 [dcl.init.list] [ Vurgu Madeni ] standardına göre :

'Std :: initializer_list' türünde bir nesne, bir başlatıcı listesinden , uygulama “N const E dizisi” türünde bir ön değer oluşturmuş ve gerçekleştirmiş gibi yapılandırılmıştır ( burada N, başlatıcı listesindeki öğe sayısıdır). Bu dizinin her öğesi, başlatıcı listesinin karşılık gelen öğesiyle kopyala başlatılır ve std :: initializer_list nesnesi, bu diziye başvurmak üzere yapılandırılır. [Not: Kopya için seçilen bir kurucuya veya dönüştürme işlevine, başlatıcı listesi bağlamında erişilebilir olmalıdır (Madde 14). - son not] Elemanlardan herhangi birini başlatmak için daraltma dönüşümü gerekiyorsa, program kötü biçimlendirilir.

Böylece, derleyiciniz meşru bir şekilde şikayet ediyor (yani, döngü için auto &saralıkta kesinti yapıp int const& satayamazsınız s).

Bu sorunu, 'std :: reference_wrapper' ile bir başlatıcı listesi (örneğin, std :: vector ') yerine bir kapsayıcı ekleyerek hafifletebilirsiniz:

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

Canlı Demo


@ Jarod42 Ouups üzgünüm, bunu değiştirdi.
101010

Eğer çözüm güzel bir çözüm için kriterlerime uymuyor - bundan memnun olsaydım ilk etapta
sormazdım

Ayrıca
teklifim sorumu

1
@darune - aslında, teklif for (auto& s : {a, b, c, d})çalışmamanızın sebebidir . Standardın neden bu maddeye sahip olduğuna gelince ..... standardizasyon komitesinin üyelerine sormanız gerekir. Bu gibi birçok şey gibi, akıl yürütme, "Özel durumunuzun rahatsız etmek için yeterince yararlı olduğunu düşünmedik" ile "arasında" herhangi bir şey olabilir. Standardın çok fazla başka bölümünün davanızı desteklemek için değişmesi gerekecek ve değerlendirmeyi erteledik "biz gelecekteki bir standardı geliştirene kadar.
Peter

Sadece kullanamaz std::array<std::reference_wrapper>>mısın?
Toby Speight

1

Sözdizimini karşılamak için

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

sarmalayıcı oluşturabilirsiniz:

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

gösteri


1
Bunun farkı std::reference_wrappernedir?
Toby Speight

1
@TobySpeight: std::reference_wrappergerektirir s.get() = 1;.
Jarod42

0

Çözüm: bir referans sarıcı kullanın

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() };
    }
};

Daha sonra şu şekilde kullanılır:

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

Bu ilk soruyu cevaplamaya çalışmaz.


-1

Referansı saklamak için sarmalayıcı sınıfı oluşturabilirsiniz ve bu değeri güncellemek için atama operatörü olacaktır:

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

Canlı demo

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.