Подтвердить что ты не робот

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

В С++ 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-стиля?

4b9b3361

Ответ 1

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

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

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

Вот живой пример.

Ответ 2

Размер 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;
    }
}

Ответ 3

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

#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

Ответ 4

Это можно сделать, но для этого нужно сделать несколько шагов. Сначала напишите 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:

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

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

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

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

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

Ответ 5

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

Если мы не можем передать размер массива функции, вместо этого мы можем передать адрес памяти, где начинается массив, со вторым адресом, где заканчивается массив. Позже, внутри функции, мы можем использовать эти 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

Ответ 6

То, что вы хотите, - это что-то вроде 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);

Довольно круто, да?