Передача функции массива std:: неизвестного размера


В C++11, как бы я мог написать функцию (или метод), которая принимает массив std::известного типа, но неизвестного размера?

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

во время моего поиска я нашел только предложения по использованию шаблонов, но они кажутся беспорядочными (определения методов в заголовке) и чрезмерными для того, что я пытаюсь выполнить.

есть ли простой способ сделать эту работу, как это было бы с простыми массивами C-стиля?

6 60

6 ответов:

есть ли простой способ сделать эту работу, как это было бы с простыми массивами C-стиля?

нет. Вы действительно не можете этого сделать, если вы не сделаете свою функцию функцией шаблон (или использовать другой вид контейнера, как std::vector, как предложено в комментариях на вопрос):

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

здесь видео.

размер array и часть типа, так что вы не можете делать то, что вы хотите. Есть несколько альтернатив.

предпочтительнее было бы взять пару итераторов:

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

кроме того, используйте vector вместо массива, что позволяет сохранить размер во время выполнения, а не как часть его типа:

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

я попробовал ниже, и это просто сработало для меня.

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

выход :

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

это можно сделать, но это займет несколько шагов, чтобы сделать чисто. Во-первых, напишите template class это представляет собой диапазон смежных значений. Потом ждем template версия, которая знает, как большой array до Impl версия, которая принимает этот непрерывный диапазон.

наконец, реализовать contig_range версия. Обратите внимание, что for( int& x: range ) работает contig_range, потому что я реализовал begin() и end() указатели и итераторы.

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

(не испытано, но дизайн должен работа.)

затем .cpp file:

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

это имеет обратную сторону, что код, который перебирает содержимое массива, не знает (во время компиляции), насколько велик массив, что может привести к оптимизации затрат. Это имеет то преимущество, что реализация не должна быть в заголовке.

будьте осторожны с явным построением contig_range, как будто вы передаете его set он будет считать, что set данные непрерывны, что является ложным, и делать неопределенное поведение повсюду. Только два std контейнеры, на которых это гарантированно работает, являются vector и array (и массивы C-стиля, как это бывает!). deque несмотря на то, что случайный доступ не является непрерывным (опасно, он непрерывен в небольших кусках!),list даже не близко, а ассоциативные (упорядоченные и неупорядоченные) контейнеры одинаково несмежны.

Итак, три конструктора я реализовал, где std::array,std::vector и C-стиль массивы, которые в основном охватывают базы.

реализация [] это легко, а между for() и [] что большая часть того, что вы хотите array, не так ли?

абсолютно, в C++11 есть простой способ написать функцию, которая принимает массив std::известного типа, но неизвестного размера.

Если мы не можем передать размер массива в функцию, то вместо этого мы можем передать адрес памяти, где начинается массив, а также 2-й адрес, где заканчивается массив. Позже, внутри функции, мы можем использовать эти 2 адреса памяти для вычисления размера массива!

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

вывод на консоль: 10, 20, 2, 4, 8

то, что вы хотите, это что-то вроде gsl::span, который доступен в библиотеке поддержки рекомендаций, описанной в основных рекомендациях C++:

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

вы можете найти реализацию GSL с открытым исходным кодом только для заголовка здесь:

https://github.com/Microsoft/GSL

С gsl::span, вы можете сделать это:

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

проблема с std::array является ли его размер частью его типа, поэтому вам придется использовать шаблон для реализации функции, которая принимает std::array произвольного размера.

gsl::span С другой стороны, сохраняет свой размер как информацию о времени выполнения. Это позволяет использовать одну не шаблонную функцию для приема массива произвольного размера. Он также будет принимать другие смежные контейнеры:

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

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

клево, да?