148

In C++11, how would I go about writing a function (or method) that takes a std::array of known type but 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 to use templates, but those seems messy (method definitions in header) and excessive for what I'm trying to accomplish.

Is there a simple way to make this work, as one would with plain C-style arrays?

Rapptz
  • 20,807
  • 5
  • 72
  • 86
Adrian
  • 2,276
  • 2
  • 19
  • 25
  • 2
    Arrays have no bounds checking or know what size they are. Therefore, you must wrap them in something or consider use of ```std::vector```. – Travis Pessetto Jun 17 '13 at 20:33
  • 22
    If templates seem messy and excessive to you, you should get over that feeling. They are commonplace in C++. – Benjamin Lindley Jun 17 '13 at 20:35
  • 1
    Any reason not to use `std::vector` as @TravisPessetto recommends? – Cory Klein Jun 17 '13 at 20:36
  • 2
    Understood. If this a limitation of their nature, I'll have to accept that. The reason I thought about avoiding std::vector (which works great for me), is that it's allocated on the heap. Since these arrays will be tiny and looped through in every iteration of the program, I thought a std::array might perform a bit better. I think I will use a C-style array then, my program is not complex. – Adrian Jun 17 '13 at 20:38
  • 19
    @Adrian Your way of thinking about performance is entirely wrong. Don't try to make micro optimizations before you even have a functional program. And after you do have a program, don't *guess* on what should be optimized, instead let a profiler tell you what part of the program should be optimized. – Paul Manta Jun 17 '13 at 20:46

6 Answers6

127

Is there a simple way to make this work, as one would with plain C-style arrays?

No. You really cannot do that unless you make your function a function template (or use another sort of container, like an 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 live example.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • 18
    The OP asks if there is any another solution but templates. – Novak Jun 17 '13 at 20:31
  • But it seems that I'm running into all kinds of errors during compilation for including a template. This function will be a method of a class with its separate .h and .cpp files. What I read online about how to implement templates in this scenario worries me. – Adrian Jun 17 '13 at 20:32
  • 3
    @Adrian: Unfortunately there is no other solution, if you want your function to work generically on arrays of any size... – Andy Prowl Jun 17 '13 at 20:35
  • @GuyDavid: I rushed into answering before reading that part, shame on me. But I think there is really no way do to this without using templates. Templates are the idiomatic solution for this kind of task – Andy Prowl Jun 17 '13 at 20:36
  • Too bad that fix didn't make it in. – chris Jun 17 '13 at 20:38
  • 3
    Correct: there is no other way. Since each std::array with a different size is a different type, you need to write a function that can work on different types. Hence, templates are the solution for std::array. – bstamour Jun 17 '13 at 20:38
  • 4
    The beautiful part about using a template here is that you can make this even more generic, so that it works with any sequence container, as well as standard arrays: `template void mulArray(C & arr, M multiplier) { /* same body */ }` – Benjamin Lindley Jun 17 '13 at 20:39
  • 2
    @BenjaminLindley: Of course, that assumes that he can put the code in the header at all. – Nicol Bolas Jun 17 '13 at 20:41
  • @AndyProwl I'm myself not familiar with a better method for this procedure, excluding templates. Good answer though. – Novak Jun 17 '13 at 20:42
  • I (and plenty of others) typically have a `tcc` file for template method definitions, and I `#include` it from the 'h' file - so definitions are separate from declarations, but it still compiles fine. – Mark K Cowan Aug 02 '15 at 17:21
  • Live example is gone, could @AndyProwl or someone with the perms update the answer with a call utilizing the given template def? – JGurtz Mar 21 '21 at 04:29
  • Why do I need **`std::size_t`**? i.e. Why do I have to write `template` rather than `template`? Thanks a lot in advance! – Milan Mar 17 '23 at 19:47
35

The size of the array is part of the type, so you can't do quite what you want. There are a couple alternatives.

Preferred would be to take a pair of iterators:

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

Alternately, use vector instead of array, which allows you to store the size at runtime rather than as part of its type:

void mulArray(std::vector<int>& arr, const int multiplier) {
    for(auto& e : arr) {
        e *= multiplier;
    }
}
Mark B
  • 95,107
  • 10
  • 109
  • 188
  • 3
    I think this is the superior solution; if you are going to go through the trouble of making a template, make it totally generic with iterators which will let you use *any* container (array,list, vector,even old school C pointers, etc) with no downside. Thanks for the hint. – Mark Lakata Jun 29 '15 at 18:04
17

EDIT

C++20 tentatively includes std::span

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

Original Answer

What you want is something like gsl::span, which is available in the Guideline Support Library described in the C++ Core Guidelines:

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

You can find an open-source header-only implementation of the GSL here:

https://github.com/Microsoft/GSL

With 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'd have to use a template in order to implement a function that takes an std::array of arbitrary size.

gsl::span on the other hand stores its size as run-time information. This allows you to use one non-template function to accept an array of arbitrary size. It will also accept other contiguous containers:

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

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

Pretty cool, huh?

suncho
  • 451
  • 3
  • 6
8

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

Output:

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
SebastianWilke
  • 470
  • 1
  • 4
  • 15
musk's
  • 113
  • 2
  • 7
  • 5
    This is not valid C++, but rather an extension. These functions are templates, even without `template`. – HolyBlackCat Mar 14 '18 at 22:32
  • 1
    I've looked into this and it appears that `auto foo(auto bar) { return bar * 2; }` is not currently valid C++ even though it compiles in GCC7 with the C++17 flag set. From reading [here](http://en.cppreference.com/w/cpp/language/auto), function parameters declared as auto are part of the Concepts TS which should eventually be part of C++20. – Fibbs May 19 '18 at 17:44
  • Warning [C26485](https://learn.microsoft.com/en-us/visualstudio/code-quality/c26485?view=vs-2019) – metablaster Oct 11 '19 at 18:17
7

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

If we are unable to pass the array size to the function, then instead, we can pass the memory address of where the array starts along with a 2nd address of where the array ends. Later, inside of 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{ 5, 10, 1, 2, 4 };

     // Provide a pointer to both the beginning and end addresses of 
     // the array.
     mulArray(iValues.begin(), iValues.end(), 2);

     return 0;
}

Output at Console:

10, 20, 2, 4, 8
SebastianWilke
  • 470
  • 1
  • 4
  • 15
2

This can be done, but it takes a few steps to do cleanly. First, write a template class that represents a range of contiguous values. Then forward a template version that knows how big the array is to the Impl version that takes this contiguous range.

Finally, implement 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 design should work).

Then, in your .cpp file:

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

This has the downside that the code that loops over the contents of the array doesn't know (at compile time) how big the array is, which could cost optimization. It has the advantage that the implementation doesn't have to be in the header.

Be careful about explicitly constructing a contig_range, as if you pass it a set it will assume that the set data is contiguous, which is false, and do undefined behavior all over the place. 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 being random access isn't contiguous (dangerously, it is contiguous in small chunks!), list is not even close, and the associative (ordered and unordered) containers are equally non-contiguous.

So the three constructors I implemented where std::array, std::vector and C-style arrays, which basically covers the bases.

Implementing [] is easy as well, and between for() and [] that is most of what you want an array for, isn't it?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Isn't this just offsetting the template to somewhere else? – GManNickG Jun 17 '13 at 20:58
  • @GManNickG sort of. The header gets a really short `template` function with next to no implementation details. The `Impl` function is not a `template` function, and so you can happily hide the implementation in the `.cpp` file of your choice. It is a really crude kind of type erasure, where I extract the ability to iterate over contiguous containers into a simpler class, and then pass that through... (while `multArrayImpl` takes a `template` as an argument, it isn't a `template` itself). – Yakk - Adam Nevraumont Jun 17 '13 at 21:01
  • I understand that this array view/array proxy class is sometimes useful. My suggestion would be to pass the begin/end of the container in the constructor so you wouldn't have to write a constructor for each container. Also I wouldn't write '&*std::begin(arr)' as dereferencing and taking the address is unnecessary here as std::begin/std::end already return an iterator. – Ricky65 Feb 18 '14 at 00:50
  • @Ricky65 If you use iterators, you need to expose the implementation. If you use pointers, you do not. The `&*` dereferences the iterator (which may not be a pointer), then makes a pointer to the address. For contiguous memory data, the pointer to `begin` and the pointer to one-past-the `end` are also random access iterators, and they are the same type for *every* contiguous range over a type `T`. – Yakk - Adam Nevraumont Feb 18 '14 at 01:44