Variable-length std::array like
Asked Answered
P

3

29

As my usually used C++ compilers allow variable-length arrays (e.g. arrays depending on runtime size), I wonder if there is something like std::array with variable size? Of course std::vector is of variable size, but it allocates on heap, and reallocates on need.

I like to have a stack allocated array with size defined at runtime. Is there any std-template that may feature this? Maybe using std::vector with a fixed maximum size?

Poeticize answered 31/12, 2013 at 12:42 Comment(17)
When using std::array, the size of the array is a template parameter, so it cannot be a runtime variable. I guess std::vector is your best bet.Astarte
Do you need to resize the array after it's constructed? Or is it just a run-time fixed size?Resident
Also, C++14 is going to have "Runtime-sized arrays with automatic storage duration".Astarte
Since there's no "stack" in the language standard, it's hard to make this question precise, or make sense.Hiers
@DanielKamilKozar: No, not any more.Hiers
@KerrekSB : That sucks donkey ass.Astarte
possible duplicate of Any alternative to std::dynarray presently available?Moriah
@DanielKamilKozar: Meh. It would be a pretty weird wart on the type system to make such a notion precise. You can see the efforts yourself in the revision history on Github. So far the idea is that "there isn't enough experience" with this, so it's being deferred.Hiers
The usual answer to this is to use std::vector with a custom allocator. Since you expect the data to be located "on the stack", presumably they will always be freed in reverse order they're allocated. It should be quite easy to write an extremely fast thread-local allocator given that restriction. The downside is that you will make a separate allocation up front to store the data, but this isn't all that different from what the OS does for your thread's stack - on a modern OS your block is virtual-only until used, just like the stack. But it won't benefit from the stack being hot in cache.Lossa
As my usually used C++ compilers allow variable-length arrays relying on this extension makes your code non-portable and non-standard.Goggles
@Walter: exactly, that's no doubt the motivation for asking the question.Lossa
Nope, but _alloca is your friend. I can't believe nobody mentioned it anywhere.Lammergeier
@user2345215: Probably since alloca doesn't work too nicely with stateful objects and this is tagged C++.Moriah
@KerrekSB: What do you mean, there's no "stack"? Automatic storage behaves just like a stack, and the language spec sometimes refers to it as a stack (e.g. "stack unwinding" when an exception is thrown), so why not call it a stack?Torque
@SteveJessop ¿Do mean, the motivation is to have non-portable code?Goggles
We all like portable code. Sometimes I like faster code even more, if it make something possible. However, fast and portable would be even better.Poeticize
@Walter: no. The question asks whether there is a standard replacement for the (non-standard) feature that the questioner knows about.Lossa
R
25

There are two proposals currently being worked on to bring run-time fixed size arrays to C++ which may be of interest to you:

  • Runtime-sized arrays with automatic storage duration. This would make runtime sized arrays a language feature (like in C11). So you could do:

      void foo(std::size_t size) {
        int arr[size];
      }
    
  • C++ Dynamic Arrays. This would bring a new container to the library, std::dynarray, which is given a fixed size at construction. It is intended to be optimized to be allocated on the stack when possible.

      void foo(std::size_t size) {
        std::dynarray<int> arr(size);
      }
    

These are both being worked on as part of an Array Extensions Technical Specification, which will be released alongside C++14.

UPDATE: std::dynarray is not implemented yet(25Aug2021).please refer to What is the status on dynarrays?

Resident answered 31/12, 2013 at 12:56 Comment(2)
So the second one looks really good. Are there implementations as of today?Poeticize
@JosephMansfield perhaps this should be merged into here ?Alexisaley
U
10

As Daniel stated in the comment, size of the std::array is specified as a template parameter, so it cannot be set during runtime.

You can though construct std::vector by passing the minimum capacity through the constructor parameter:

#include <vector>

int main(int argc, char * argv[])
{
    std::vector<int> a;
    a.reserve(5);
    std::cout << a.capacity() << "\n";
    std::cout << a.size();

    getchar();
}

But. Still vector's contents will be stored on the heap, not on the stack. The problem is, that compiler has to know, how much space should be allocated for the function prior to its execution, so it is simply not possible to store variable-length data on the stack.

Umbelliferous answered 31/12, 2013 at 12:48 Comment(3)
What you do is not really set the minimum capacity through the constructor parameter. What this does is set an initial size and since size<=capacity this works. To set the capacity use reserve.Moriah
@BenjaminBannier You're right. Effectively the goal would be reached, but the solution with reserve is a lot more elegant.Umbelliferous
This is heap based, which is the reason I don't want it, and asked my question... However, if the std::vector could be implemented to allocate it's inital capacity on stack, and switch to heap on the first reallocation. This will not work by reserve, as it has to be done on first allocation... but would be cool. However I doubt it works this way in the usual implementation.Poeticize
D
2

Maybe using std::vector with a fixed maximal size?

If boost is allowed then boost::container::static_vector and boost::container::small_vector are the closest I can think of

boost::container::static_vector<int, 1024> my_array;
boost::container::small_vector<int, 1024> my_vector;

static_vector is a sequence container like boost::container::vector with contiguous storage that can change in size, along with the static allocation, low overhead, and fixed capacity of boost::array.

The size of each object is still fixed but it can be worth it if the number of allocations is significant and/or the item count is small

If the vector can grow beyond the limit then just use boost::container::small_vector. The heap is only touched when the size is larger than the defined limit

small_vector is a vector-like container optimized for the case when it contains few elements. It contains some preallocated elements in-place, which can avoid the use of dynamic storage allocation when the actual number of elements is below that preallocated threshold.


If you use Qt then QVarLengthArray is another way to go:

QVarLengthArray is an attempt to work around this gap in the C++ language. It allocates a certain number of elements on the stack, and if you resize the array to a larger size, it automatically uses the heap instead. Stack allocation has the advantage that it is much faster than heap allocation.

Example:

int myfunc(int n)
{
    QVarLengthArray<int, 1024> array(n + 1);
    ...
    return array[n];
}

Some other similar solutions:


If a 3rd party solution isn't allowed then you can roll your own solution by wrapping std::array in a struct to get a static vector

template<typename T, size_t N> 
struct my_static_vector
{
    explicit static_vector(size_t size) { } // ...
    size_t size() const noexcept { return curr_size; }
    static size_t capacity() const noexcept { return N; }
    T& operator[](size_t pos) { return data[pos]; }
    void push_back(const T& value) { data[curr_size++] = value; }
    // ...
private:
    std::array<typename T, N> data;
    std::size_t curr_size;
}

And if small_vector is required then you can use std::variant to contain both the my_static_vector and the vector

template<typename T, size_t N> 
struct my_small_vector
{
    explicit small_vector(size_t size) { } // ...
    size_t size() const noexcept {
        if (data.index() == 0) {
            return data.get<0>().size();
        } else {
            return data.get<1>().size();
        }
    }
    static size_t capacity() const noexcept {
        if (data.index() == 0) {
            return data.get<0>().capacity();
        } else {
            return data.get<1>().capacity();
        }
    }
    // ...
private:
    std::variant<my_static_vector<T, N>, std::vector<T>> data;
}
Dilly answered 25/1, 2022 at 14:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.