基于范围的for,在非const值上使用大括号初始化式
Range-based for with brace-initializer over non-const values?
我试图迭代一些std::list
s,对它们进行排序。这是朴素的方法:
#include<list>
using namespace std;
int main(void){
list<int> a,b,c;
for(auto& l:{a,b,c}) l.sort();
}
生产aa.cpp:5:25: error: no matching member function for call to 'sort'
for(auto& l:{a,b,c}) l.sort();
~~^~~~
/usr/bin/../lib64/gcc/x86_64-linux-gnu/4.9/../../../../include/c++/4.9/bits/stl_list.h:1586:7: note:
candidate function not viable: 'this' argument has type 'const
std::list<int, std::allocator<int> >', but method is not marked const
sort();
^
/usr/bin/../lib64/gcc/x86_64-linux-gnu/4.9/../../../../include/c++/4.9/bits/stl_list.h:1596:9: note:
candidate function template not viable: requires 1 argument, but 0 were
provided
sort(_StrictWeakOrdering);
^
1 error generated.
我是否正确地猜测大括号初始化器正在创建这些列表的副本?有没有一种方法可以不复制它们,并在循环中修改它们?(除了制作指向它们的指针列表,这是我目前的解决方案)。
你猜对了。std::initializer_list
元素总是const
(这使得sort()
不可能,因为sort()
是非const
成员函数),并且它的元素总是被复制(这将使sort()
-ing它们没有意义,即使它们不是const
)。从[dcl.init。列表],强调我的:
类型为
std::initializer_list<E>
的对象从初始化列表中构造,就像实现一样分配了一个包含N个元素的临时数组,类型为const E,其中N为数组中元素的个数初始化器列表。该数组的每个元素都是用初始化式的相应元素复制初始化列表,并且构造std::initializer_list<E>
对象来引用该数组。[注意:构造函数为副本选择的转换函数应在初始化式上下文中可访问(第11条)列表。 -end note]如果初始化任何元素都需要窄化转换,则程序为不规范的。(例子:
struct X {
X(std::initializer_list<double> v);
};
X x{ 1,2,3 };
初始化的实现方式大致相当于:
const double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));
假设实现可以用一对指针构造一个
initializer_list
对象。端例]
没有办法使它们成为非const或非复制的。指针解决方案有效:
for (auto l : {&a, &b, &c}) l->sort();
因为指针是const,而不是它所指向的元素。另一种方法是编写可变函数模板:
template <typename... Lists>
void sortAll(Lists&&... lists) {
// before C++17
using expander = int[];
expander{0, (void(lists.sort()), 0)...};
// C++17 or later
(lists.sort(), ...);
}
sortAll(a, b, c);
你也可以,我猜,写一个帮助器来包装你的列表到一个数组的reference_wrapper
到list<int>
(因为你不能有一个数组的引用),但这可能更令人困惑比有用:
template <typename List, typename... Lists>
std::array<std::reference_wrapper<List>, sizeof...(Lists) + 1>
as_array(List& x, Lists&... xs) {
return {x, xs...};
}
for (list<int>& l : as_array(a, b, c)) { // can't use auto, that deduces
l.sort(); // reference_wrapper<list<int>>,
} // so would need l.get().sort()
可以编写一个函数ref_range
,它允许您这样做:
for(auto& l : ref_range(a,b,c)) {
l.sort();
}
正如其他人所说,一旦你写了{a,b,c}
,你就被initializer_list
困住了,这样的列表总是复制它的参数。副本是const
(因此你的错误),但即使你能得到一个非const
参考,你也会修改a
, b
和c
的副本,而不是原件。
无论如何,这里是ref_range
。构建reference_wrapper
的vector
。
// http://stackoverflow.com/questions/31724863/range-based-for-with-brace-initializer-over-non-const-values
#include<list>
#include<functional>
#include<array>
template<typename T, std:: size_t N>
struct hold_array_of_refs {
using vec_type = std:: array< std:: reference_wrapper<T>, N >;
vec_type m_v_of_refs;
hold_array_of_refs(vec_type && v_of_refs) : m_v_of_refs(std::move(v_of_refs)) { }
~hold_array_of_refs() { }
struct iterator {
typename vec_type :: const_iterator m_it;
iterator(typename vec_type :: const_iterator it) : m_it(it) {}
bool operator != (const iterator &other) {
return this->m_it != other.m_it;
}
iterator& operator++() { // prefix
++ this->m_it;
return *this;
}
T& operator*() {
return *m_it;
}
};
iterator begin() const {
return iterator(m_v_of_refs.begin());
}
iterator end() const {
return iterator(m_v_of_refs.end());
}
};
template<typename... Ts>
using getFirstTypeOfPack = typename std::tuple_element<0, std::tuple<Ts...>>::type;
template<typename ...T>
auto ref_range(T&... args) -> hold_array_of_refs< getFirstTypeOfPack<T...> , sizeof...(args)> {
return {{{ std:: ref(args)... }}}; // Why does clang prefer three levels of {} ?
}
#include<iostream>
int main(void){
std:: list<int> a,b,c;
// print the addresses, so we can verify we're dealing
// with the same objects
std:: cout << &a << std:: endl;
std:: cout << &b << std:: endl;
std:: cout << &c << std:: endl;
for(auto& l : ref_range(a,b,c)) {
std:: cout << &l << std:: endl;
l.sort();
}
}
{...}
语法实际上是创建一个std::initializer_list
。如链接页面所述:
std::initializer_list
对象在下列情况下自动构造:
- […]
- a 带括号的init-list被绑定到
中auto
,包括在范围for循环
:
类型为
std::initializer_list<T>
的对象是一个轻量级代理对象,它提供对类型为const T
的对象数组的访问。
因此,您不能修改通过该initialize_list
访问的对象。你用指针的解决方案对我来说是最简单的。
直接回答你的问题:
我猜对了吗这些列表?
是的,这是第一个问题。您的代码将创建列表的副本,对这些副本进行排序,并最终忘记已排序的副本。
然而,单独这样做只会导致代码无法工作。编译器错误提示了第二个问题:l
的隐式类型是list<int> const&
,而不是list<int>&
。因此,编译器报错sort()
试图修改常量列表。
你可以用一个讨厌的const_cast
:
#include <list>
#include <iostream>
using namespace std;
int main(void){
list<int> a,b,c;
a.push_back(2);
a.push_back(0);
a.push_back(1);
for(auto& l:{a,b,c}) const_cast<list<int>&>(l).sort();
for(auto i:a) cout << i << endl;
}
然而,这将触发第一个问题:列表的列表包含副本,并且只有这些副本被排序。所以最后的输出不是你想要的:
2
0
1
最简单的解决方法是创建一个指向列表的指针列表:
#include <list>
#include <iostream>
using namespace std;
int main(void){
list<int> a,b,c;
a.push_back(2);
a.push_back(0);
a.push_back(1);
for(auto l:{&a,&b,&c}) l->sort();
for(auto i:a) cout << i << endl;
}
这将产生期望的结果:
0
1
2
其他人已经提到了std::reference_wrapper
,但他们随后使用它来创建STL容器,而不是坚持使用大括号初始化列表。
所以你需要做的就是:
for(auto& l:{std::ref(a),std::ref(b),std::ref(c)}) l.get().sort();
当然,这与已经建议的指针解决方案非常相似。
- 如何在旧c++中初始化const-std向量
- 我可以初始化 const 实例,以便我可以将其用作 const 来初始化数组吗?
- 使用 const char* 初始化 const ref 字符串成员时幕后会发生什么
- 如何使用函数的输出初始化 const 数组结构字段?
- 有效地初始化 const std::vector 类成员
- 正在初始化const boost multi_array
- 在类声明中初始化 const 成员变量时在调试模式下出现异常
- 如何在现代C++中用生成器初始化 const 容器?
- 初始化 const 成员的正确方法
- 如何通过头文件中的函数初始化 const int 数组?
- 通过与现有 QHash 连接来初始化 const QHash
- C++ 在头文件或构造函数中初始化 const 类成员变量?
- 在没有构造函数的情况下初始化 const c++ 类
- 如何初始化 const std::vector<MyClass>
- 使用静态方法初始化 const 类字段的做法是好是坏
- 在C++标准中哪里说必须初始化 const 内置类型变量的定义
- 从constructor参数中初始化const多维数组中的const多维数组
- 初始化const 3D数组成员变量
- 在同一构造函数中使用该 const int 初始化 const int 和对象
- 在构造函数中初始化const字段,但首先检查一个参数