example - vector c++
Pasar un std:: array de tamaño desconocido a una función (6)
En C ++ 11, ¿cómo podría escribir una función (o método) que tome una matriz estándar :: de tipo conocido pero de tamaño desconocido?
// made up example
void mulArray(std::array<int, ?>& arr, const int multiplier) {
for(auto& e : arr) {
e *= multiplier;
}
}
// lets imagine these being full of numbers
std::array<int, 17> arr1;
std::array<int, 6> arr2;
std::array<int, 95> arr3;
mulArray(arr1, 3);
mulArray(arr2, 5);
mulArray(arr3, 2);
Durante mi búsqueda, solo encontré sugerencias para usar plantillas, pero esas parecen desordenadas (definiciones de métodos en el encabezado) y excesivas para lo que intento lograr.
¿Hay una manera simple de hacer que esto funcione, como lo haría con las matrices de estilo C simples?
¿Hay una manera simple de hacer que esto funcione, como lo haría con las matrices de estilo C simples?
No. Realmente no puedes hacer eso a menos que conviertas tu función en una plantilla de función (o utilices otro tipo de contenedor, como un std::vector
, como se sugiere en los comentarios a la pregunta):
template<std::size_t SIZE>
void mulArray(std::array<int, SIZE>& arr, const int multiplier) {
for(auto& e : arr) {
e *= multiplier;
}
}
Aquí hay un ejemplo en vivo .
Absolutamente, hay una manera simple en C ++ 11 para escribir una función que toma una matriz std :: de tipo conocido, pero de tamaño desconocido.
Si no podemos pasar el tamaño de la matriz a la función, en su lugar, podemos pasar la dirección de la memoria de donde comienza la matriz junto con una segunda dirección donde termina la matriz. Más tarde, dentro de la función, podemos usar estas 2 direcciones de memoria para calcular el tamaño de la matriz.
#include <iostream>
#include <array>
// The function that can take a std::array of any size!
void mulArray(int* piStart, int* piLast, int multiplier){
// Calculate the size of the array (how many values it holds)
unsigned int uiArraySize = piLast - piStart;
// print each value held in the array
for (unsigned int uiCount = 0; uiCount < uiArraySize; uiCount++)
std::cout << *(piStart + uiCount) * multiplier << std::endl;
}
int main(){
// initialize an array that can can hold 5 values
std::array<int, 5> iValues;
iValues[0] = 5;
iValues[1] = 10;
iValues[2] = 1;
iValues[3] = 2;
iValues[4] = 4;
// Provide a pointer to both the beginning and end addresses of
// the array.
mulArray(iValues.begin(), iValues.end(), 2);
return 0;
}
Salida en la consola: 10, 20, 2, 4, 8
El tamaño de la array
es parte del tipo , por lo que no puede hacer exactamente lo que quiere. Hay un par de alternativas.
Preferido sería tomar un par de iteradores:
template <typename Iter>
void mulArray(Iter first, Iter last, const int multiplier) {
for(; first != last; ++first) {
*first *= multiplier;
}
}
Alternativamente, use vector
lugar de array, que le permite almacenar el tamaño en tiempo de ejecución en lugar de como parte de su tipo:
void mulArray(std::vector<int>& arr, const int multiplier) {
for(auto& e : arr) {
e *= multiplier;
}
}
Esto se puede hacer, pero se necesitan unos pocos pasos para hacerlo limpiamente. Primero, escriba una template class
que represente un rango de valores contiguos. A continuación, envíe una versión de template
que sepa qué tan grande es la array
a la versión de Impl
que toma este rango contiguo.
Finalmente, implemente la versión contig_range
. Tenga en cuenta que for( int& x: range )
funciona para contig_range
, porque implementé begin()
y end()
y los punteros son iteradores.
template<typename T>
struct contig_range {
T* _begin, _end;
contig_range( T* b, T* e ):_begin(b), _end(e) {}
T const* begin() const { return _begin; }
T const* end() const { return _end; }
T* begin() { return _begin; }
T* end() { return _end; }
contig_range( contig_range const& ) = default;
contig_range( contig_range && ) = default;
contig_range():_begin(nullptr), _end(nullptr) {}
// maybe block `operator=`? contig_range follows reference semantics
// and there really isn''t a run time safe `operator=` for reference semantics on
// a range when the RHS is of unknown width...
// I guess I could make it follow pointer semantics and rebase? Dunno
// this being tricky, I am tempted to =delete operator=
template<typename T, std::size_t N>
contig_range( std::array<T, N>& arr ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {}
template<typename T, std::size_t N>
contig_range( T(&arr)[N] ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {}
template<typename T, typename A>
contig_range( std::vector<T, A>& arr ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {}
};
void mulArrayImpl( contig_range<int> arr, const int multiplier );
template<std::size_t N>
void mulArray( std::array<int, N>& arr, const int multiplier ) {
mulArrayImpl( contig_range<int>(arr), multiplier );
}
(no probado, pero el diseño debería funcionar).
Luego, en su archivo .cpp
:
void mulArrayImpl(contig_range<int> rng, const int multiplier) {
for(auto& e : rng) {
e *= multiplier;
}
}
Esto tiene la desventaja de que el código que recorre los contenidos de la matriz no sabe (en tiempo de compilación) qué tan grande es la matriz, lo que podría costarle una optimización. Tiene la ventaja de que la implementación no tiene que estar en el encabezado.
Tenga cuidado al construir explícitamente un contig_range
, ya que si lo pasa un set
, supondrá que los datos set
son contiguos, lo que es falso, y que realizan un comportamiento indefinido en cualquier lugar. Los únicos dos contenedores std
que está garantizado que funcionan son el vector
y el array
(¡y las matrices estilo C, como ocurre!). deque
pesar de que el acceso aleatorio no es contiguo (¡peligrosamente, es contiguo en pequeños fragmentos!), la list
ni siquiera está cerca, y los contenedores asociativos (ordenados y no ordenados) son igualmente no contiguos.
Entonces, los tres constructores que implementé fueron std::array
, std::vector
y C-style arrays, que básicamente cubren las bases.
La implementación de []
es fácil, y entre for()
y []
esa es la mayor parte de lo que desea una array
, ¿no?
Intenté a continuación y funcionó para mí.
#include <iostream>
#include <array>
using namespace std;
// made up example
void mulArray(auto &arr, const int multiplier)
{
for(auto& e : arr)
{
e *= multiplier;
}
}
void dispArray(auto &arr)
{
for(auto& e : arr)
{
std::cout << e << " ";
}
std::cout << endl;
}
int main()
{
// lets imagine these being full of numbers
std::array<int, 7> arr1 = {1, 2, 3, 4, 5, 6, 7};
std::array<int, 6> arr2 = {2, 4, 6, 8, 10, 12};
std::array<int, 9> arr3 = {1, 1, 1, 1, 1, 1, 1, 1, 1};
dispArray(arr1);
dispArray(arr2);
dispArray(arr3);
mulArray(arr1, 3);
mulArray(arr2, 5);
mulArray(arr3, 2);
dispArray(arr1);
dispArray(arr2);
dispArray(arr3);
return 0;
}
SALIDA:
1 2 3 4 5 6 7
2 4 6 8 10 12
1 1 1 1 1 1 1 1 1
3 6 9 12 15 18 21
10 20 30 40 50 60
2 2 2 2 2 2 2 2 2
Lo que desea es algo como gsl::span
, que está disponible en la Biblioteca de soporte de pautas descrita en las Directrices básicas de C ++:
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#SS-views
Aquí puede encontrar una implementación de encabezado abierto solo de GSL:
https://github.com/Microsoft/GSL
Con gsl::span
, puedes hacer esto:
// made up example
void mulArray(gsl::span<int>& arr, const int multiplier) {
for(auto& e : arr) {
e *= multiplier;
}
}
// lets imagine these being full of numbers
std::array<int, 17> arr1;
std::array<int, 6> arr2;
std::array<int, 95> arr3;
mulArray(arr1, 3);
mulArray(arr2, 5);
mulArray(arr3, 2);
El problema con std::array
es que su tamaño es parte de su tipo, por lo que tendría que usar una plantilla para implementar una función que tome una std::array
de tamaño arbitrario.
gsl::span
otro lado, gsl::span
almacena su tamaño como información de tiempo de ejecución. Esto le permite usar una función que no sea de plantilla para aceptar una matriz de tamaño arbitrario. También aceptará otros contenedores contiguos:
std::vector<int> vec = {1, 2, 3, 4};
int carr[] = {5, 6, 7, 8};
mulArray(vec, 6);
mulArray(carr, 7);
Genial, ¿eh?