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

В 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?

70

Решение

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

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

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

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

67

Другие решения

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

Безусловно, в C ++ 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

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

3

Это можно сделать, но для этого нужно выполнить несколько шагов. Сначала напишите 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 данные являются смежными, что является ложным, и повсеместно выполняют неопределенное поведение. Только два std контейнеры, на которых это гарантированно будет работать, vector а также array (и массивы в стиле C, как это бывает!). deque несмотря на то, что произвольный доступ не является смежным (опасно, он является смежным в маленьких кусках!), list даже не близко, и ассоциативные (упорядоченные и неупорядоченные) контейнеры одинаково несмежны.

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

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

1

РЕДАКТИРОВАТЬ

C ++ 20 ориентировочно включает std::span

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

Оригинальный ответ

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

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

1
По вопросам рекламы [email protected]