c++ - sentencia - Inicializando std:: vector con llamadas de función iterativas
sentencia de iteración (5)
En muchos idiomas, hay generadores que ayudan a inicializar las colecciones. En C ++, si uno quiere inicializar un vector uniformemente, puede escribir:
std::vector<int> vec(10, 42); // get 10 elements, each equals 42
¿Qué pasa si uno quiere generar diferentes valores sobre la marcha? Por ejemplo, inicialízalo con 10 valores aleatorios, o números consecutivos del 0 al 9? Esta sintaxis sería conveniente, pero no funciona en C ++ 11:
int cnt = 0;
std::vector<int> vec(10, [&cnt]()->int { return cnt++;});
¿Hay una buena manera de inicializar una colección mediante llamadas a funciones iterativas? Actualmente utilizo este patrón feo (no mucho más legible / corto que un bucle):
std::vector<int> vec;
int cnt = 0;
std::generate_n(std::back_inserter(vec), 10, [&cnt]()->int { return cnt++;});
Hay una cosa que ayudaría, y explicaría la falta del primer constructor. Puedo imaginar un iterador que toma una función y número de llamadas, para que el constructor
vector ( InputIterator first, InputIterator last);
sería aplicable. Pero no encontré nada como esto en la biblioteca estándar. ¿Me lo perdí? ¿Hay alguna otra razón por la que el primer constructor no llegó a la norma?
El mundo es demasiado grande para que C ++ envíe una solución para todo. Sin embargo, C ++ no quiere ser un gran supermercado lleno de comidas preparadas para todos los paladares concebibles. Más bien, es una cocina pequeña y bien equipada en la que usted , el Master Chef de C ++, puede preparar cualquier solución que desee.
Aquí hay un ejemplo tonto y muy crudo de un generador de secuencias:
#include <iterator>
struct sequence_iterator : std::iterator<std::input_iterator_tag, int>
{
sequence_iterator() : singular(true) { }
sequence_iterator(int a, int b) : singular(false) start(a), end(b) { }
bool singular;
int start;
int end;
int operator*() { return start; }
void operator++() { ++start; }
bool operator==(sequence_iterator const & rhs) const
{
return (start == end) == rhs.singular;
}
bool operator!=(sequence_iterator const & rhs) const
{
return !operator==(rhs);
}
};
Ahora puedes decir:
std::vector<int> v(sequence_iterator(1,10), sequence_iterator());
De la misma manera, puede escribir un gadget más general que "llame a un funtor dado un número determinado de veces", etc. (por ejemplo, tome un objeto de función mediante una copia de plantilla y use los contadores como contadores de repetición; ).
Lamentablemente, no hay una instalación estándar para hacer esto.
Para su ejemplo específico, podría usar el counting_iterator de Boost.Iterator de la siguiente manera:
std::vector<int> v(boost::counting_iterator<int>(0),
boost::counting_iterator<int>(10));
O incluso con Boost. Rango como este:
auto v(boost::copy_range<std::vector<int>>(boost::irange(0,10)));
( copy_range
básicamente solo return std::vector<int>(begin(range), end(range))
y es una excelente manera de adoptar la construcción de rango completo para contenedores existentes que solo admiten la construcción de rango con dos iteradores).
Ahora, para el caso de propósito general con una función de generador (como std::rand
), está el function_input_iterator
. Cuando se incrementa, llama al generador y guarda el resultado, que luego se devuelve al anular la referencia.
#include <vector>
#include <iostream>
#include <cmath>
#include <boost/iterator/function_input_iterator.hpp>
int main(){
std::vector<int> v(boost::make_function_input_iterator(std::rand, 0),
boost::make_function_input_iterator(std::rand,10));
for(auto e : v)
std::cout << e << " ";
}
Lamentablemente, dado que function_input_iterator
no usa Boost.ResultOf, necesita un puntero de función o un tipo de objeto de función que tenga un tipo de resultado anidado . Lambdas, por la razón que sea, no tienen eso. Podría pasar la lambda a un objeto std::function
(o boost::function
), que lo define. Aquí hay un ejemplo con std::function
. Solo se puede esperar que Boost.Iterator haga uso de Boost.ResultOf algún día, que utilizará decltype
si se define BOOST_RESULT_OF_USE_DECLTYPE
.
Nadie mencionó boost::assign , así que lo presentaré aquí:
Ejemplo
#include <iostream>
#include <vector>
#include <boost/assign/std/vector.hpp>
#include <cstdlib>
int main()
{
std::vector<int> v1;
std::vector<int> v2;
boost::assign::push_back(v1).repeat_fun(9, &rand);
int cnt = 0;
boost::assign::push_back(v2).repeat_fun(10, [&cnt]()->int { return cnt++;});
for (auto i : v1)
{
std::cout << i << '' '';
}
std::cout << std::endl;
for (auto i : v2)
{
std::cout << i << '' '';
}
}
Salida
41 18467 6334 26500 19169 15724 11478 29358 26962
0 1 2 3 4 5 6 7 8 9
Puedes usar SFINAE para formar una tabla:
#include <iostream>
#include <vector>
template <int n> struct coeff { static int const value = coeff<n-1>::value + 3; };
template <> struct coeff<0> { static int const value = 0; };
template<int... values> struct c1 {static int const value[sizeof...(values)];};
template<int... values> int const c1<values...>::value[] = {values...};
template<int n, int... values> struct c2 : c2< n-1, coeff<n-1>::value, values...> {};
template<int... values> struct c2< 0, values... > : c1<values...> {};
template<int n> struct table : c2< n > {
static std::vector< unsigned int > FormTable()
{
return std::vector< unsigned int >( & c2< n >::value[0], & c2< n >::value[n] );
}
};
int main()
{
const auto myTable = table< 20 >::FormTable();
for ( const auto & it : myTable )
{
std::cout<< it << std::endl;
}
}
Si está usando un compilador que soporta lambdas como lo usa en su pregunta, entonces es muy probable que también incluya std::iota
, que al menos hace que el caso de conteo sea un poco más limpio:
std::vector <int> vec(10);
std::iota(begin(vec), end(vec), 0);
Para este escenario (y muchos otros, creo), realmente preferiríamos un iota_n
:
namespace stdx {
template <class FwdIt, class T>
void iota_n(FwdIt b, size_t count, T val = T()) {
for ( ; count; --count, ++b, ++val)
*b = val;
}
}
Que, para tu caso, utilizarías como:
std::vector<int> vec;
stdx::iota_n(std::back_inserter(vec), 10);
En cuanto a por qué esto no estaba incluido en la biblioteca estándar, realmente no puedo adivinarlo. Supongo que esto podría verse como un argumento a favor de los rangos, por lo que el algoritmo tomaría un rango, y tendríamos una manera fácil de crear un rango desde un par de inicio / final o un par de inicio / conteo. No estoy seguro de estar completamente de acuerdo con eso, aunque los rangos parecen funcionar bien en algunos casos, pero en otros tienen poco o ningún sentido. No estoy seguro de que sin más trabajo, tenemos una respuesta que es mucho mejor que un par de iteradores.