Passer un tableau std :: array de taille inconnue à une fonction

En C ++ 11, comment pourrais-je écrire une fonction (ou une méthode) qui prend un tableau std :: de type connu mais de taille inconnue?

// made up example void mulArray(std::array& arr, const int multiplier) { for(auto& e : arr) { e *= multiplier; } } // lets imagine these being full of numbers std::array arr1; std::array arr2; std::array arr3; mulArray(arr1, 3); mulArray(arr2, 5); mulArray(arr3, 2); 

Pendant ma recherche, je n’ai trouvé que des suggestions pour utiliser des modèles, mais celles-ci semblent désordonnées (définitions de méthodes dans l’en-tête) et excessives pour ce que j’essaie d’accomplir.

Existe-t-il un moyen simple de faire fonctionner ce système, comme on le ferait avec des tableaux simples de style C?

Existe-t-il un moyen simple de faire fonctionner ce système, comme on le ferait avec des tableaux simples de style C?

Non, vous ne pouvez vraiment pas faire cela à moins de faire de votre fonction un modèle de fonction (ou d’utiliser un autre type de conteneur, comme un std::vector , comme suggéré dans les commentaires à la question):

 template void mulArray(std::array& arr, const int multiplier) { for(auto& e : arr) { e *= multiplier; } } 

Voici un exemple en direct .

La taille du array fait partie du type , vous ne pouvez donc pas faire ce que vous voulez. Il y a quelques alternatives.

Préféré serait de prendre une paire d’iterators:

 template  void mulArray(Iter first, Iter last, const int multiplier) { for(; first != last; ++first) { *first *= multiplier; } } 

Vous pouvez également utiliser vector au lieu de array, ce qui vous permet de stocker la taille à l’exécution plutôt que dans le cadre de son type:

 void mulArray(std::vector& arr, const int multiplier) { for(auto& e : arr) { e *= multiplier; } } 

J’ai essayé ci-dessous et ça a juste fonctionné pour moi.

 #include  #include  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 arr1 = {1, 2, 3, 4, 5, 6, 7}; std::array arr2 = {2, 4, 6, 8, 10, 12}; std::array 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; } 

SORTIE:

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

Cela peut être fait, mais il faut quelques étapes pour le faire proprement. Tout d’abord, écrivez une template class qui représente une plage de valeurs contiguës. Ensuite, transférez une version de template qui connaît la taille du array à la version Impl prenant cette plage contiguë.

Enfin, implémentez la version contig_range . Notez que for( int& x: range ) fonctionne pour contig_range , car j’ai implémenté begin() et end() et les pointeurs sont des iterators.

 template 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 sortingcky, I am tempted to =delete operator= template contig_range( std::array& arr ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {} template contig_range( T(&arr)[N] ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {} template contig_range( std::vector& arr ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {} }; void mulArrayImpl( contig_range arr, const int multiplier ); template void mulArray( std::array& arr, const int multiplier ) { mulArrayImpl( contig_range(arr), multiplier ); } 

(non testé, mais la conception devrait fonctionner).

Ensuite, dans votre fichier .cpp :

 void mulArrayImpl(contig_range rng, const int multiplier) { for(auto& e : rng) { e *= multiplier; } } 

L’inconvénient est que le code qui boucle sur le contenu du tableau ne connaît pas (au moment de la compilation) la taille du tableau, ce qui pourrait entraîner une optimisation des coûts. L’avantage est que l’implémentation n’a pas besoin d’être dans l’en-tête.

Faites attention à ne pas construire explicitement un contig_range , car si vous lui passez un set il supposera que les données de l’ set sont contiguës, ce qui est faux, et que son comportement est indéfini partout. Les deux seuls conteneurs std lesquels il est garanti de travailler sont les array vector et les array (et les tableaux de type C, comme cela arrive!). deque malgré le fait que l’access aléatoire n’est pas contigu (dangereusement, il est contigu en petits morceaux!), la list n’est même pas proche, et les conteneurs associatifs (ordonnés et non ordonnés) sont également non contigus.

Donc, les trois constructeurs que j’ai implémentés std::array , std::vector et C-style, qui couvrent essentiellement les bases.

L’implémentation de [] est également simple, et entre for() et [] c’est ce que vous voulez pour un array , n’est-ce pas?

Absolument, il existe un moyen simple en C ++ 11 d’écrire une fonction qui prend un std :: array de type connu, mais de taille inconnue.

Si nous ne sums pas en mesure de transmettre la taille du tableau à la fonction, alors nous pouvons transmettre l’adresse de la mémoire où le tableau commence avec une deuxième adresse où le tableau se termine. Plus tard, à l’intérieur de la fonction, nous pouvons utiliser ces 2 adresses mémoire pour calculer la taille du tableau!

 #include  #include  // 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 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; } 

Sortie à la console: 10, 20, 2, 4, 8

Ce que vous voulez, c’est quelque chose comme gsl::span , qui est disponible dans Guideline Support Library décrit dans les Directives C ++ Core:

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#SS-views

Vous pouvez trouver ici une implémentation open-source de l’en-tête uniquement du GSL:

https://github.com/Microsoft/GSL

Avec gsl::span , vous pouvez le faire:

 // made up example void mulArray(gsl::span& arr, const int multiplier) { for(auto& e : arr) { e *= multiplier; } } // lets imagine these being full of numbers std::array arr1; std::array arr2; std::array arr3; mulArray(arr1, 3); mulArray(arr2, 5); mulArray(arr3, 2); 

Le problème avec std::array est que sa taille fait partie de son type, vous devrez donc utiliser un template pour implémenter une fonction qui prend un std::array de taille arbitraire.

gsl::span autre part, gsl::span stocke sa taille en tant qu’information d’exécution. Cela vous permet d’utiliser une fonction non-template pour accepter un tableau de taille arbitraire. Il acceptera également d’autres conteneurs contigus:

 std::vector vec = {1, 2, 3, 4}; int carr[] = {5, 6, 7, 8}; mulArray(vec, 6); mulArray(carr, 7); 

Assez cool, hein?