Passing std :: array of unknown function size

In C ++ 11, how can I write a function (or method) that takes a std :: array of a known type, but of unknown size?

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

During my search, I only found suggestions for using templates, but they seem messy (method definitions in the header) and excessive for what I'm trying to do.

Is there an easy way to make this work, like regular C-style arrays?

+61
c ++ c ++ 11 stdarray
Jun 17 '13 at 20:29
source share
6 answers

Is there an easy way to make this work, like regular C-style arrays?

No. You really cannot do this unless you make your function a function template (or use a different kind of container, for example std::vector , as suggested in the comments to the question):

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

Here is a living example .

+61
Jun 17 '13 at 20:30
source share

The size of an array is part of the type, so you cannot do what you want. There are several alternatives.

It would be preferable to take a couple of iterators:

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

As an alternative, use vector instead of an array that allows you to store the size at run time, and not as part of its type:

 void mulArray(std::vector<int>& arr, const int multiplier) { for(auto& e : arr) { e *= multiplier; } } 
+20
Jun 17 '13 at 20:43
source share

I tried below and it just worked for me.

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

EXIT:

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
Jun 24 '17 at 20:39 on
source share

This can be done, but you need to take a few steps. First write a template class that represents a range of contiguous values. Then move the version of template , which knows how large the array for the version of Impl that accepts this continuous range.

Finally, run the contig_range version. Note that for( int& x: range ) works for contig_range because I implemented begin() and end() , and pointers are iterators.

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

(not tested, but the design should work).

Then in the .cpp file:

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

This has the disadvantage that the code that iterates over the contents of the array does not know (at compile time) how large the array is, which may cost optimization. This has the advantage that the implementation does not have to be in the header.

Be careful with the explicit construction of contig_range , as if you passed it a set , it would assume that the set data is contiguous, which is false, and execute undefined behavior everywhere. The only two std containers that this is guaranteed to work on are vector and array (and C-style arrays, as it happens!). deque , despite the fact that random access is not contiguous (dangerous, it is contiguous in small pieces!), list is not even close, and associative (ordered and disordered) containers are equally disjoint.

So, there are three constructors that I implemented where std::array , std::vector and C-style arrays that basically cover the bases.

The implementation of [] also simple, and between for() and [] , which is most suitable for array , isn't it?

+1
Jun 17 '13 at 20:55 on
source share

Absolutely, in C ++ 11 there is an easy way to write a function that takes a std :: array of a known type, but of an unknown size.

If we cannot pass the size of the array to the function, instead we can pass the memory address where the array begins, with a second address where the array ends. Later, inside the function, we can use these 2 memory addresses to calculate the size of the array!

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

Console Output: 10, 20, 2, 4, 8

+1
Apr 23 '18 at 2:24
source share

What you want is something like gsl::span , which is available in the Guide Support Library described in the C ++ Fundamentals:

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

Here you can find the open source GSL implementation:

https://github.com/Microsoft/GSL

Using gsl::span you can do this:

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

The problem with std::array is that its size is part of its type, so you will need to use a template to implement a function that accepts std::array arbitrary size.

gsl::span on the other hand saves its size as runtime information. This allows you to use one function without a template to accept an array of arbitrary size. He will also accept other adjacent containers:

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

Pretty cool, huh?

0
Mar 14 '18 at 22:08
source share



All Articles