ช่วงไร้เดียงสาที่ใช้สำหรับลูปไม่ทำงาน


11

ต่อไปนี้ไม่ได้รวบรวม:

#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

ข้อผิดพลาดของคอมไพเลอร์คือ: error: assignment of read-only reference 's'

ตอนนี้ในกรณีของฉันจริงรายการทำจากตัวแปรสมาชิกในชั้นเรียน

ทีนี้มันไม่ได้ผลเพราะนิพจน์กลายเป็นinitializer_list<int>สิ่งที่คัดลอก a, b, c และ d - ดังนั้นจึงไม่อนุญาตให้ทำการดัดแปลง

คำถามของฉันคือสองเท่า:

มีแรงจูงใจเบื้องหลังที่ไม่ยอมให้เขียนช่วงสำหรับลูปด้วยวิธีนี้หรือไม่? เช่น. อาจจะมีกรณีพิเศษสำหรับการแสดงออกวงเล็บปีกกาเปล่า

อะไรคือวิธีการสร้างประโยคที่ซับซ้อนในการแก้ไขลูปประเภทนี้?

จะมีบางสิ่งที่ชอบในบรรทัดนี้:

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

ฉันไม่พิจารณาตัวชี้ร้ายเป็นทางออกที่ดี (นั่นคือ{&a, &b, &c, &d}) - การแก้ปัญหาใด ๆ ควรให้การอ้างอิงองค์ประกอบโดยตรงเมื่อ iterator คือ de-อ้างอิง


1
การแก้ปัญหาง่าย (ว่าฉันจะไม่ใช้ตัวเองจริงๆ) { &a, &b, &c, &d }คือการสร้างรายการของตัวชี้แทน:
โปรแกรมเมอร์บางคนเพื่อน

2
initializer_listส่วนใหญ่เป็นมุมมองเกี่ยวกับconstอาร์เรย์
Jarod42

สิ่งที่ฉันอาจจะทำคือการเริ่มต้นตัวแปรอย่างชัดเจนทีละหนึ่ง มันจะไม่มากไปกว่าการเขียนมันชัดเจนและชัดเจนและมันทำในสิ่งที่ตั้งใจ :)
โปรแกรมเมอร์บางคนเพื่อน

3
หากคุณไม่ต้องการ{ &a, &b, &c, &d }คุณจะไม่ต้องการ:for (auto& s : std::initializer_list<std::reference_wrapper<int>>{a, b, c, d}) { s.get() = 1; }
Jarod42

คำถามที่ว่า "ทำไมจึงไม่สามารถทำงานได้" เป็นคำถามที่แตกต่างจาก "ฉันจะทำอย่างไรเพื่อให้ได้งานนี้"
Nicol Bolas

คำตอบ:


4

ช่วงไม่วิเศษเท่าที่คนต้องการ ในท้ายที่สุดจะต้องมีวัตถุที่คอมไพเลอร์สามารถสร้างสายในทั้งฟังก์ชั่นสมาชิกหรือฟังก์ชั่นฟรีและbegin()end()

ที่ใกล้ที่สุดคุณอาจจะสามารถที่จะมาคือ:

#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
std::vector<int*>คุณสามารถวาง
Jarod42

@hhollomon ฉันกล่าวอย่างชัดเจนว่าฉันไม่สนใจวิธีการแก้ปัญหาตัวชี้ทางอ้อม
darune

1
มันควรจะเป็นauto sหรือไม่auto* s auto& s
LF

@darune - ฉันยินดีที่จะให้ใครสักคนให้คำตอบที่แตกต่างกัน ไม่ชัดเจนว่าคำตอบนั้นมีอยู่ในมาตรฐานปัจจุบัน
mhhollomon

@LF - ตกลง
mhhollomon

4

อีกวิธีหนึ่งในแนวคิดของ wrapper:

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

แล้ว:

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

เอาท์พุท

0000
1111

2
นี่เป็นวิธีแก้ปัญหา / วิธีแก้ปัญหาที่ดีและดี มันเป็นความคิดที่คล้ายคลึงกับคำตอบของฉัน (ฉันใช้ std :: reference_wrapper และไม่ได้ใช้เทมเพลต variadic)
38599

4

ตามมาตรฐาน§11.6.4การเริ่มต้นรายการ / p5 [dcl.init.list] [ เหมืองเน้นการ :

วัตถุประเภท 'std :: initializer_list' ถูกสร้างขึ้นจากรายการ initializer ราวกับว่าการสร้างและการนำไปใช้งานจริง (7.4) ค่า prvalue ของประเภท "อาร์เรย์ของ N const E"โดยที่ N คือจำนวนขององค์ประกอบในรายการ initializer แต่ละองค์ประกอบของอาร์เรย์นั้นถูกคัดลอกเริ่มต้นด้วยองค์ประกอบที่สอดคล้องกันของรายการ initializer และวัตถุ std :: initializer_list ถูกสร้างขึ้นเพื่ออ้างถึงอาร์เรย์นั้น [หมายเหตุ: คอนสตรัคเตอร์หรือฟังก์ชั่นการแปลงที่เลือกไว้สำหรับการคัดลอกจะสามารถเข้าถึงได้ (ข้อ 14) ในบริบทของรายการ initializer - หมายเหตุสุดท้าย] หากจำเป็นต้องทำการแปลงให้แคบลงเพื่อเริ่มต้นองค์ประกอบใด ๆ โปรแกรมจะไม่ทำงาน

ดังนั้นคอมไพเลอร์ของคุณบ่นอย่างถูกต้อง (เช่นauto &sหักint const& sและคุณไม่สามารถกำหนดให้sอยู่ในช่วงสำหรับลูป)

คุณสามารถบรรเทาปัญหานี้ได้โดยการแนะนำคอนเทนเนอร์แทนรายการ initializer (เช่น `std :: vector ') ด้วย' 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;
}

การสาธิตสด


@ Jarod42 Ouups ขอโทษแก้ไขเพิ่มเติมว่า
101010

วิธีการแก้ปัญหาของคุณไม่ตรงกับเกณฑ์ของฉันสำหรับวิธีการแก้ปัญหาที่ดี - ถ้าฉันมีความสุขกับที่ฉันจะไม่ได้ถามในสถานที่แรก :)
38914

คำพูดของคุณก็ไม่ได้พยายามตอบคำถามของฉันด้วย
darune

1
@darune - อันที่จริงการอ้างถึงคือเหตุผลที่คุณfor (auto& s : {a, b, c, d})ไม่ทำงาน ทำไมมาตรฐานจึงมีประโยคนั้น ..... คุณต้องถามสมาชิกของคณะกรรมการมาตรฐาน เช่นเดียวกับหลาย ๆ สิ่งการให้เหตุผลอาจเป็นอะไรก็ได้ระหว่าง "เราไม่ได้พิจารณากรณีของคุณโดยเฉพาะว่ามีประโยชน์มากพอที่จะใส่ใจ" ถึง "ถึง" ส่วนอื่น ๆ ของมาตรฐานมากเกินไปจะต้องเปลี่ยนเพื่อรองรับกรณีของคุณและเราเลื่อนการพิจารณา ทั้งหมดจนกว่าเราจะพัฒนามาตรฐานในอนาคต "
ปีเตอร์

คุณใช้std::array<std::reference_wrapper>>ไม่ได้เหรอ
Toby Speight

1

เพื่อตอบสนองไวยากรณ์ที่

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

คุณอาจสร้างเสื้อคลุม:

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

การสาธิต


1
มันแตกต่างstd::reference_wrapperอย่างไร?
Toby Speight

1
@TobySpeight: จะต้องมีstd::reference_wrapper s.get() = 1;
Jarod42

0

การแก้ไข: ใช้กระดาษห่ออ้างอิง

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

จากนั้นใช้เป็น:

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

นี่ไม่ได้พยายามตอบคำถามแรกเลย


-1

คุณสามารถสร้างคลาส wrapper สำหรับการจัดเก็บข้อมูลอ้างอิงและจะมีผู้ดำเนินการที่ได้รับมอบหมายเพื่อปรับปรุงค่านี้:

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

การสาธิตสด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.