基于范围的带有括号初始化器的非常量值?
Range-based for with brace-initializer over non-const values?
我正在尝试迭代多个 std::list
,对它们中的每一个进行排序。这是天真的方法:
#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.list],强调我的:
An object of type std::initializer_list<E>
is constructed from an initializer list as if the implementation
allocated a temporary array of N elements of type 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<E>
object is constructed to refer to that array. [ Note: A constructor
or conversion function selected for the copy shall be accessible (Clause 11) 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. [ Example:
struct X {
X(std::initializer_list<double> v);
};
X x{ 1,2,3 };
The initialization will be implemented in a way roughly equivalent to this:
const double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));
assuming that the implementation can construct an initializer_list
object with a pair of pointers. —end
example ]
无法使它们成为非常量或非复制。指针解决方案有效:
for (auto l : {&a, &b, &c}) l->sort();
因为 指针 是常量,而不是它指向的元素。另一种选择是编写可变函数模板:
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()
{...}
语法实际上是在创建一个 std::initializer_list
。正如链接页面所述:
A std::initializer_list
object is automatically constructed when:
- [...]
- a braced-init-list is bound to
auto
, including in a ranged for loop
并且:
An object of type std::initializer_list<T>
is a lightweight proxy object that provides access to an array of objects of type const T
.
因此,您无法修改通过此 initialize_list
访问的对象。你的解决方案对我来说是最简单的。
直接回答您的问题:
Am I correctly guessing that brace-initializer is creating copy of
those lists?
是的,这是第一个问题。您的代码将创建列表的副本,对这些副本进行排序,最后忘记排序的副本。
但是,仅此一项只会导致无法运行的代码。编译器错误提示了第二个问题: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
可以编写一个函数 ref_range
来执行此操作:
for(auto& l : ref_range(a,b,c)) {
l.sort();
}
正如其他人所说,一旦您编写 {a,b,c}
,您就会陷入 initializer_list
,并且这样的列表总是会复制其参数。副本是 const
(因此你的错误),但即使你可以获得非 const
引用,你也会修改 a
、b
和 c
而不是原来的。
总之,这里是ref_range
。它构建了 vector
of reference_wrapper
.
//
#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::reference_wrapper
,但他们随后使用它创建了一个 STL 容器,而不是坚持使用大括号初始化列表。
所以你需要做的就是:
for(auto& l:{std::ref(a),std::ref(b),std::ref(c)}) l.get().sort();
当然,这与已经建议的指针解决方案非常相似。
我正在尝试迭代多个 std::list
,对它们中的每一个进行排序。这是天真的方法:
#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.list],强调我的:
An object of type
std::initializer_list<E>
is constructed from an initializer list as if the implementation allocated a temporary array of N elements of type 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 thestd::initializer_list<E>
object is constructed to refer to that array. [ Note: A constructor or conversion function selected for the copy shall be accessible (Clause 11) 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. [ Example:
struct X {
X(std::initializer_list<double> v);
};
X x{ 1,2,3 };
The initialization will be implemented in a way roughly equivalent to this:
const double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));
assuming that the implementation can construct an
initializer_list
object with a pair of pointers. —end example ]
无法使它们成为非常量或非复制。指针解决方案有效:
for (auto l : {&a, &b, &c}) l->sort();
因为 指针 是常量,而不是它指向的元素。另一种选择是编写可变函数模板:
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()
{...}
语法实际上是在创建一个 std::initializer_list
。正如链接页面所述:
A
std::initializer_list
object is automatically constructed when:
- [...]
- a braced-init-list is bound to
auto
, including in a ranged for loop
并且:
An object of type
std::initializer_list<T>
is a lightweight proxy object that provides access to an array of objects of typeconst T
.
因此,您无法修改通过此 initialize_list
访问的对象。你的解决方案对我来说是最简单的。
直接回答您的问题:
Am I correctly guessing that brace-initializer is creating copy of those lists?
是的,这是第一个问题。您的代码将创建列表的副本,对这些副本进行排序,最后忘记排序的副本。
但是,仅此一项只会导致无法运行的代码。编译器错误提示了第二个问题: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
可以编写一个函数 ref_range
来执行此操作:
for(auto& l : ref_range(a,b,c)) {
l.sort();
}
正如其他人所说,一旦您编写 {a,b,c}
,您就会陷入 initializer_list
,并且这样的列表总是会复制其参数。副本是 const
(因此你的错误),但即使你可以获得非 const
引用,你也会修改 a
、b
和 c
而不是原来的。
总之,这里是ref_range
。它构建了 vector
of reference_wrapper
.
//
#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::reference_wrapper
,但他们随后使用它创建了一个 STL 容器,而不是坚持使用大括号初始化列表。
所以你需要做的就是:
for(auto& l:{std::ref(a),std::ref(b),std::ref(c)}) l.get().sort();
当然,这与已经建议的指针解决方案非常相似。