基于无辜范围的循环无效

以下内容编译:

#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

编译器错误是:error: assignment of read-only reference 's'

在我的实际情况下,列表由类中的成员变量组成。

现在,这不起作用,因为表达式变成实际上复制a,b,c和d的initializer_list<int>,因此也不允许修改。

我的问题有两个:

不允许以这种方式编写基于范围的for循环背后有动机吗? 例如。也许会有一种特殊的裸括号表达方式。

什么是修复此类循环的语法整洁方法?

最好采用这种方式:

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

我认为指针间接寻址不是一个好的解决方案(即{&a,&b,&c,&d}),任何解决方案都应在取消迭代器引用时直接为元素提供引用。

lihuizi0806 回答:基于无辜范围的循环无效

范围并不像人们所希望的那样神奇。最后,必须有一个对象,编译器可以生成对成员函数或自由函数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;
}
,

根据标准§11.6.4List-initialization / p5 [dcl.init.list] [重点矿山]:

  

“ std :: initializer_list”类型的对象是通过   初始化程序列表,好像实现已生成并实现   (7.4)类型为“ N const E的数组” 的prvalue,其中N是   初始化列表中的元素。该数组的每个元素是   使用初始化程序的相应元素进行复制初始化   列表,并且构造了std :: initializer_list对象以引用   到那个数组。 [注意:已选择构造函数或转换函数   在以下情况下,该副本应可访问(第14条)   初始化列表。 —尾注]如果需要缩小转换   要初始化任何元素,程序的格式是错误的。

因此,您的编译器在进行正当的抱怨(即auto &s扣除到int const& s,并且您不能在范围for循环中分配给s)。

您可以通过引入容器而不是带有“ std :: reference_wrapper”的初始化列表(例如“ std :: vector”)来缓解此问题:

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

int main()
{
    int a{},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

,

包装方法中的另一个解决方案:

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{},d{};

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

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

输出

0000
1111
,

要满足该语法

for (auto& s : something{a,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;     
};

Demo

,

解决方案:使用参考包装器

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,d}) {
    e = 1;
}

这不会尝试回答第一个问题。

,

您可以创建包装类来存储引用,并且包装类将具有赋值运算符来更新此值:

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{},d{};

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

Live demo

本文链接:https://www.f2er.com/3152130.html

大家都在问