web-dev-qa-db-fra.com

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

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

// 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);

Au cours de mes recherches, je n'ai trouvé que des suggestions d'utilisation de modèles, mais celles-ci semblent compliquées (définitions de méthodes dans l'en-tête) et excessives par rapport à ce que j'essaie d'accomplir.

Y at-il un moyen simple de faire ce travail, comme on le ferait avec des tableaux simples de style C?

76
Adrian

Y at-il un moyen simple de faire ce travail, comme on le ferait avec des tableaux simples de style C?

Non. Vous ne pouvez vraiment pas faire cela à moins de transformer votre fonction en fonction template (ou d'utiliser un autre type de conteneur, comme un std::vector, comme suggéré dans les commentaires sur la question):

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

Voici un exemple en direct .

70
Andy Prowl

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

Il serait préférable de prendre une paire d'itérateurs:

template <typename Iter>
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 au moment de l'exécution plutôt que dans son type:

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

J'ai essayé ci-dessous et cela a fonctionné pour moi.

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

SORTIE:

1 2 3 4 5 6 7

2 4 6 8 10 12

1 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 2

4
musk's

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 parvenons pas à transmettre la taille du tableau à la fonction, nous pouvons également indiquer l'adresse de la mémoire où commence le tableau, ainsi qu'une deuxième adresse indiquant l'endroit où se termine le tableau. Plus tard, à l'intérieur de la fonction, nous pouvons utiliser ces 2 adresses de mémoire pour calculer la taille du tableau!

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

sortie sur la console: 10, 20, 2, 4, 8

2
David M. Helmuth

[~ # ~] éditer [~ # ~]

C++ 20 inclut provisoirement std::span

https://en.cppreference.com/w/cpp/container/span

Réponse originale

Ce que tu veux, c'est quelque chose comme gsl::span, disponible dans la bibliothèque de prise en charge de lignes directrices décrite dans les directives de base C++:

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

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

https://github.com/Microsoft/GSL

Avec gsl::span, tu peux le faire:

// 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);

Le problème avec std::array est que sa taille fait partie de son type, vous devez donc utiliser un modèle pour implémenter une fonction prenant un std::array de taille arbitraire.

gsl::span d'autre part stocke sa taille en tant qu'informations 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<int> vec = {1, 2, 3, 4};
int carr[] = {5, 6, 7, 8};

mulArray(vec, 6);
mulArray(carr, 7);

Assez cool, hein?

2
suncho

Cela peut être fait, mais il faut quelques étapes pour le faire proprement. Commencez par écrire un template class Qui représente une plage de valeurs contiguës. Transférez ensuite une version template qui connaît la taille de la array vers la version Impl qui prend 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 itérateurs.

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 );
}

(non testé, mais le design devrait fonctionner).

Ensuite, dans votre fichier .cpp:

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

Cela a l'inconvénient que le code qui parcourt le contenu du tableau ne sait pas (au moment de la compilation) quelle est la taille du tableau, ce qui pourrait coûter cher à l'optimisation. Cela présente l'avantage que l'implémentation ne doit pas nécessairement figurer dans l'en-tête.

Méfiez-vous de la construction explicite d'un contig_range, Car si vous lui transmettez un set, les données set sont contiguës, ce qui est faux, et le comportement non défini est omniprésent. le lieu. Les deux seuls std conteneurs sur lesquels il est garanti que cela fonctionne sont vector et array (et des tableaux de style C, comme cela arrive!). deque bien que l'accès aléatoire ne soit pas contigu (dangereusement, il est contigu par petits morceaux!), list n'est même pas proche et les conteneurs associatifs (ordonnés et non ordonnés) sont également non contigu.

Ainsi, les trois constructeurs que j'ai implémentés, où les tableaux std::array, std::vector Et C-style, qui couvrent essentiellement les bases.

Implémenter [] Est également facile, et entre for() et [], Vous voulez un array, n'est-ce pas?

1