Innocent range based for loop not working

不想你离开。 提交于 2019-12-10 12:33:52

问题


The following does not compile:

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

Try it on godbolt

Compiler error is: error: assignment of read-only reference 's'

Now in my actual case the list is made of member variables on a class.

Now, this doesn't work because the expression becomes an initializer_list<int> that actually copies a,b,c, and d - hence also not allowing modification.

My question is two-fold:

Is there any motivation behind not allowing to write a range-based for loop in this way ? eg. perhaps there could be a special case for naked brace expressions.

What is a syntactical neat way of fixing this type of loop ?

Something along this line would be preferred:

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

I do not consider pointer indirection a good solution (that is {&a, &b, &c, &d}) - any solution should give the element reference directly when the iterator is de-referenced.


回答1:


Ranges are not as magic as people would like. In the end, there must be an object that the compiler can generate calls on to either a member function or free function begin() and end().

Closest you'll probably be able to come is:

#include <iostream>

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

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



回答2:


Just another solution within a wrapper idea:

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

Then:

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

outputs

0000
1111



回答3:


According to the standard §11.6.4 List-initialization/p5 [dcl.init.list] [Emphasis Mine]:

An object of type 'std::initializer_list' is constructed from an initializer list as if the implementation generated and materialized (7.4) a prvalue of type “array of N const E”, where N is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and the std::initializer_list object is constructed to refer to that array. [ Note: A constructor or conversion function selected for the copy shall be accessible (Clause 14) in the context of the initializer list. — end note ] If a narrowing conversion is required to initialize any of the elements, the program is ill-formed.

Thus, your compiler is complaining legitimately (i.e., auto &s deducts to int const& s and you cannot assign to s in the ranged for loop).

You could alleviate this problem by introducing a container instead of an initializer list (e.g., `std::vector’) with ‘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;
}

Live Demo




回答4:


To satisfy that syntax

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

you might create wrapper:

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




回答5:


Solution: use a reference wrapper

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

Then used as:

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

This doesn't try to answer the first question though.




回答6:


You could create wrapper class for storing reference and which will have assignment operator to update this value:

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

Live demo



来源:https://stackoverflow.com/questions/58730476/innocent-range-based-for-loop-not-working

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!