c++ - tipo - ¿Basado en el rango para con el inicializador de brace sobre valores no constantes?
tipos de datos en c++ (4)
Es posible escribir una función ref_range
que le permite hacer esto:
for(auto& l : ref_range(a,b,c)) {
l.sort();
}
Como han dicho otros, una vez que escribes {a,b,c}
te quedas con una lista de initializer_list
, y esa lista siempre toma copias de sus argumentos. Las copias son const
(de ahí su error), pero incluso si pudiera obtener una referencia no const
, estaría modificando las copias de a
, b
c
lugar de las originales.
De todos modos, aquí está ref_range
. Construye un vector
de reference_wrapper
.
// 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();
}
}
Estoy tratando de recorrer una serie de std::list
s, ordenando cada uno de ellos. Este es el enfoque ingenuo:
#include<list>
using namespace std;
int main(void){
list<int> a,b,c;
for(auto& l:{a,b,c}) l.sort();
}
productor
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.
¿Estoy adivinando correctamente que Brace-Initializer está creando una copia de esas listas? ¿Y hay una manera de no copiarlos, y hacerlos modificables dentro del bucle? (aparte de hacer una lista de punteros a ellos, que es mi solución actual).
Estás adivinando correctamente. std::initializer_list
elementos std::initializer_list
siempre son const
(lo que hace que su sort()
sea imposible, ya que sort()
es una función miembro no const
) y sus elementos siempre se copian (lo que haría que sort()
no tenga sentido incluso si no eran const
. De [dcl.init.list], énfasis mío:
Un objeto de tipo
std::initializer_list<E>
se construye a partir de una lista de inicializadores como si la implementación asignara una matriz temporal de N elementos de tipo const E , donde N es el número de elementos en la lista de inicializadores. Cada elemento de esa matriz se inicializa con copia con el elemento correspondiente de la lista de inicializadores, y el objetostd::initializer_list<E>
se construye para referirse a esa matriz. [Nota: Un constructor o una función de conversión seleccionada para la copia debe ser accesible (Cláusula 11) en el contexto de la lista de inicializadores. —Descripción final] Si se requiere una conversión de reducción para inicializar cualquiera de los elementos, el programa está mal formado. [Ejemplo:
struct X { X(std::initializer_list<double> v); }; X x{ 1,2,3 };
La inicialización se implementará de una manera aproximadamente equivalente a esto:
const double __a[3] = {double{1}, double{2}, double{3}}; X x(std::initializer_list<double>(__a, __a+3));
asumiendo que la implementación puede construir un objeto
initializer_list
con un par de punteros. —En ejemplo]
No hay forma de hacerlos no constantes o no copiados. La solución de puntero funciona:
for (auto l : {&a, &b, &c}) l->sort();
porque es el puntero que es constante, no el elemento al que apunta. La otra alternativa sería escribir una plantilla de función variable:
template <typename... Lists>
void sortAll(Lists&&... lists) {
using expander = int[];
expander{0, (void(lists.sort()), 0)...};
}
sortAll(a, b, c);
También podría, supongo, escribir un ayudante para envolver sus listas en una matriz de reference_wrapper
para list<int>
(ya que no puede tener una matriz de referencias), pero esto probablemente sea más confuso que útil:
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()
La sintaxis {...}
realidad está creando una std::initializer_list
. Como dice la página enlazada:
Un objeto
std::initializer_list
se construye automáticamente cuando:
- [...]
- una lista-iniciada está vinculada a
auto
, incluso en un rango para bucle
Y
Un objeto de tipo
std::initializer_list<T>
es un objeto de proxy ligero que proporciona acceso a una matriz de objetos de tipoconst T
Por lo tanto, no puede modificar los objetos a los que se accede a través de esta initialize_list
. Tus soluciones con los punteros me parecen la más fácil.
Respuesta directa a tu pregunta:
¿Estoy adivinando correctamente que Brace-Initializer está creando una copia de esas listas?
Sí, este es el primer problema. Su código creará copias de sus listas, las ordenará y finalmente olvidará las copias ordenadas.
Sin embargo , eso solo resultaría en un código que no funciona. El error del compilador sugiere un segundo problema: el tipo implícito de l
es list<int> const&
, en lugar de list<int>&
. Entonces el compilador se queja de que sort()
intenta modificar las listas constantes.
Puedes solucionar este segundo problema con un desagradable 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;
}
Sin embargo, eso provocará el primer problema: su lista de listas contiene copias, y solo esas copias están ordenadas. Así que la salida final no es lo que quieres:
2
0
1
La solución más sencilla es crear una lista de punteros a sus listas:
#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;
}
Esto producirá el resultado deseado:
0
1
2