I am trying to iterate over a number of std::list
s, sorting each of them. This is the naive approach:
#include<list>
using namespace std;
int main(void){
list<int> a,b,c;
for(auto& l:{a,b,c}) l.sort();
}
producing
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.
Am I correctly guessing that brace-initializer is creating copy of those lists? And is there a way to not copy them, and make them modifiable inside the loop? (other than making list of pointers to them, which is my current workaround).
You are guessing correctly. std::initializer_list
elements are always const
(which makes sort()
ing them impossible, as sort()
is a non-const
member function) and its elements are always copied (which would make sort()
-ing them meaningless even if they weren't const
). From [dcl.init.list], emphasis mine:
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 ]
There is no way to make them non-const or non-copied. The pointer solution works:
for (auto l : {&a, &b, &c}) l->sort();
because it's the pointer that's const, not the element it's pointing to. The other alternative would be to write a variadic function template:
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);
You could also, I guess, write a helper to wrap your lists into an array of reference_wrapper
to list<int>
(since you can't have an array of references), but this is probably more confusing than helpful:
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()