基于无辜范围的循环无效


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的a,因此也不允许修改。

我的问题有两个:

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

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

最好遵循以下方式:

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

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


1
一个简单的解决方法(我不会真的用我自己)是创建指针的列表,而不是:{ &a, &b, &c, &d }
一些程序员花花公子

2
initializer_list主要是关于const数组的视图。
Jarod42 '19

我可能会做的就是显式地初始化变量,一个接一个。它不会写得多,它清晰明了,并且可以实现预期的目的。:)
一些程序员花花公子

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

Answers:


4

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

@mhhollomon我明确指出我对指针间接解决方案不感兴趣。
darune

1
应该是auto sauto* s不是auto& s
LF

@darune-我很高兴有人可以给出不同的答案。目前的标准尚不存在这样的答案。
mhhollomon

@LF-同意。
mhhollomon

4

包装概念中的另一种解决方案:

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而不使用可变参数模板)
darune

4

根据标准§11.6.4List-initialization / p5 [dcl.init.list] [ Emphasis Mine ]:

从初始化器列表构造类型为'std :: initializer_list'的对象,就好像该实现生成并实现了(7.4)类型为“ N const E array”的prvalue,其中N是初始化器列表中元素的数量。该数组的每个元素都使用初始化列表的相应元素进行复制初始化,并且构造std :: initializer_list对象以引用该数组。[注意:为副本选择的构造函数或转换函数应在初始化列表的上下文中可访问(第14节)。—结束说明]如果需要缩小转换范围以初始化任何元素,则程序格式错误。

因此,您的编译器在合法地进行抱怨(即,在范围内的for循环中auto &s扣除,int const& s而您不能分配给s)。

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

#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抱歉,对此进行了修改。
9101年

您的解决方案不符合我寻求一个好的解决方案的标准-如果我对此感到满意,那么我一开始就不会提出要求:)
darune

您的报价也没有试图回答我的问题
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 '19

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

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

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

现场演示

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.