Phạm vi vô tội dựa trên vòng lặp không hoạt động


11

Sau đây không biên dịch:

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

Hãy thử nó trên godbolt

Lỗi trình biên dịch là: error: assignment of read-only reference 's'

Bây giờ trong trường hợp thực tế của tôi, danh sách được tạo từ các biến thành viên trên một lớp.

Bây giờ, điều này không hoạt động vì biểu thức trở thành một initializer_list<int>bản sao thực sự sao chép a, b, c và d - do đó cũng không cho phép sửa đổi.

Câu hỏi của tôi là hai lần:

Có động lực nào đằng sau việc không cho phép viết một vòng lặp dựa trên phạm vi theo cách này không? ví dụ. có lẽ có thể có một trường hợp đặc biệt cho biểu hiện niềng răng trần.

Một cách gọn gàng cú pháp để sửa chữa loại vòng lặp này là gì?

Một cái gì đó dọc theo dòng này sẽ được ưa thích:

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

Tôi không coi việc xác định con trỏ là một giải pháp tốt (nghĩa là {&a, &b, &c, &d}) - bất kỳ giải pháp nào cũng nên cung cấp tham chiếu phần tử trực tiếp khi trình lặp được hủy tham chiếu .


1
Một cách giải quyết đơn giản (mà tôi không thực sự sử dụng bản thân mình) là tạo một danh sách các con trỏ thay thế : { &a, &b, &c, &d }.
Một số lập trình viên anh chàng

2
initializer_listchủ yếu là một cái nhìn trên constmảng.
Jarod42

Những gì tôi có thể sẽ làm là khởi tạo rõ ràng các biến, từng biến một. Nó sẽ không còn nhiều để viết, nó rõ ràng và rõ ràng, và đó là những gì dự định. :)
Một số lập trình viên anh chàng

3
nếu bạn không muốn { &a, &b, &c, &d }, bạn sẽ không muốn:for (auto& s : std::initializer_list<std::reference_wrapper<int>>{a, b, c, d}) { s.get() = 1; }
Jarod42

Các câu hỏi "tại sao điều này không thể làm việc" là một câu hỏi rất khác với "tôi có thể làm gì để làm một cái gì đó như thế này?"
Nicol Bolas

Câu trả lời:


4

Phạm vi không phải là phép thuật như mọi người muốn. Cuối cùng, phải có một đối tượng mà trình biên dịch có thể tạo các lệnh gọi tới hàm thành viên hoặc hàm tự do begin()end().

Gần nhất có lẽ bạn sẽ có thể đến là:

#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ạn có thể thả std::vector<int*>.
Jarod42

@mhhollomon Tôi tuyên bố rõ ràng tôi không quan tâm đến một giải pháp chỉ dẫn con trỏ.
darune

1
Nó nên auto shay auto* skhông auto& s.
LF

@darune - Tôi sẽ rất vui khi có ai đó đưa ra một câu trả lời khác. Không rõ ràng rằng một câu trả lời như vậy tồn tại với tiêu chuẩn hiện tại.
mhhollomon

@LF - đồng ý.
mhhollomon

4

Chỉ là một giải pháp khác trong một ý tưởng bao bọc:

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

Sau đó:

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

đầu ra

0000
1111

2
Đây là một giải pháp / giải pháp tốt và tốt. Đó là một ý tưởng tương tự với câu trả lời của riêng tôi (Tôi đã sử dụng std ::
Reference_wrapper

4

Theo tiêu chuẩn §11.6.4 Khởi tạo danh sách / p5 [dcl.init.list] [ Nhấn mạnh mỏ ]:

Một đối tượng thuộc loại 'std :: initizer_list' được xây dựng từ danh sách trình khởi tạo như thể việc triển khai được tạo ra và được vật chất hóa (7.4) một giá trị của mảng Kiểu kiểu N của E const , trong đó N là số phần tử trong danh sách khởi tạo. Mỗi phần tử của mảng đó được sao chép khởi tạo với phần tử tương ứng của danh sách trình khởi tạo và đối tượng std :: initizer_list được xây dựng để tham chiếu đến mảng đó. [Lưu ý: Hàm tạo hoặc hàm chuyển đổi được chọn cho bản sao sẽ có thể truy cập được (Điều 14) trong ngữ cảnh của danh sách trình khởi tạo. - lưu ý cuối] Nếu cần chuyển đổi thu hẹp để khởi tạo bất kỳ phần tử nào, chương trình sẽ không được định dạng.

Do đó, trình biên dịch của bạn đang khiếu nại một cách hợp pháp (nghĩa là auto &skhấu trừ int const& svà bạn không thể gán cho strong vòng lặp ranged for).

Bạn có thể giảm bớt vấn đề này bằng cách giới thiệu một thùng chứa thay vì danh sách trình khởi tạo (ví dụ: `std :: vector ') với' 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;
}

Bản thử trực tiếp


@ Jarod42 Ôi xin lỗi, sửa đổi mà.
101010

Giải pháp của bạn không phù hợp với tiêu chí của tôi cho một giải pháp tốt đẹp - nếu tôi hài lòng với điều đó tôi sẽ không hỏi ngay từ đầu :)
darune

Ngoài ra, trích dẫn của bạn không cố gắng trả lời câu hỏi của tôi
darune 6/11/19

1
@darune - thực ra, trích dẫn là lý do mà bạn for (auto& s : {a, b, c, d})không làm việc. Về lý do tại sao tiêu chuẩn có điều khoản đó ..... bạn phải hỏi các thành viên của ủy ban tiêu chuẩn hóa. Giống như nhiều điều như vậy, lý do có thể là bất cứ điều gì giữa "Chúng tôi không coi trường hợp cụ thể của bạn đủ hữu ích để giải quyết" thông qua "Quá nhiều phần khác của tiêu chuẩn sẽ phải thay đổi để hỗ trợ cho trường hợp của bạn và chúng tôi đã hoãn xem xét của tất cả điều đó cho đến khi chúng tôi phát triển một tiêu chuẩn trong tương lai ".
Peter

Bạn không thể sử dụng std::array<std::reference_wrapper>>?
Toby Speight

1

Để đáp ứng cú pháp đó

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

bạn có thể tạo trình bao bọc:

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

Bản giới thiệu


1
Điều đó khác với std::reference_wrappernhư thế nào?
Toby Speight

1
@TobySpeight: std::reference_wrappersẽ yêu cầu s.get() = 1;.
Jarod42

0

Giải pháp: sử dụng trình bao bọc tham chiếu

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

Sau đó được sử dụng như:

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

Điều này không cố gắng trả lời câu hỏi đầu tiên mặc dù.


-1

Bạn có thể tạo lớp trình bao bọc để lưu trữ tham chiếu và sẽ có toán tử gán để cập nhật giá trị này:

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

Bản thử trực tiếp

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.