提问人:darune 提问时间:11/6/2019 最后编辑:L. F.darune 更新时间:11/6/2019 访问量:689
基于 for 循环的无辜范围不起作用
Innocent range based for loop not working
问:
以下内容不编译:
#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;
}
编译器错误是:error: assignment of read-only reference 's'
现在,在我的实际案例中,列表由类上的成员变量组成。
现在,这不起作用,因为表达式变成了实际上复制 a、b、c 和 d 的表达式 - 因此也不允许修改。initializer_list<int>
我的问题是双重的:
不允许以这种方式编写基于范围的for循环背后有什么动机吗? 例如,也许裸括号表达式可能有一个特殊情况。
修复此类循环的语法简洁方法是什么?
最好是这样的内容:
for (auto& s : something(a, b, c, d)) {
s = 1;
}
我不认为指针间接是一个好的解决方案(即{&a,&b,&c,&d}
) - 当迭代器被取消引用时,任何解决方案都应该直接提供元素引用。
答:
范围并不像人们想要的那么神奇。最后,必须有一个对象,编译器可以对其生成对成员函数或自由函数的调用,并且 .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;
}
评论
std::vector<int*>
auto s
auto* s
auto& s
根据标准 §11.6.4 List-initialization/p5 [dcl.init.list] [Emphasis Mine]:
类型为“std::initializer_list”的对象由 初始值设定项列表,就好像实现生成并具体化一样 (7.4) 类型为“N const E 数组”的 prvalue,其中 N 是 元素。该数组的每个元素都是 使用初始值设定项的相应元素进行复制初始化 列表,并将 std::initializer_list 对象构造为引用 添加到该数组。[ 注意:选择了构造函数或转换函数 因为副本应在 初始值设定项列表。— 尾注 ] 如果需要缩小转换 要初始化任何元素,程序格式不正确。
因此,你的编译器在合理地抱怨(即,在范围的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;
}
评论
for (auto& s : {a, b, c, d})
std::array<std::reference_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
满足该语法
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;
};
评论
std::reference_wrapper
std::reference_wrapper
s.get() = 1;
只是包装器创意中的另一个解决方案:
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
评论
解决方案:使用引用包装器
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;
}
不过,这并没有试图回答第一个问题。
评论
{ &a, &b, &c, &d }
initializer_list
主要是对数组的看法。const
{ &a, &b, &c, &d }
for (auto& s : std::initializer_list<std::reference_wrapper<int>>{a, b, c, d}) { s.get() = 1; }