tipos tipo float español ejemplo definicion declaracion datos dato constantes c++ c++11 initializer-list

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 objeto std::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 tipo const 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