C99 introduced the notion of variable length arrays: stack allocated built-in arrays whose size is determined at runtime. C++ lacks a similar feature, to the discontent of many a programmer. However, two recent proposals for adding dynamic arrays and runtime-sized arrays to C++14 are closing the gap at last. Learn how to use these new features to imitate C99’s variable length arrays in C++.
Just as you’ve grown used to C++11, a new standard dubbed C+14 is seen on the horizon. Fret not, though, for the new standard doesn’t bring any revolutionary features such as rvalue references or multithreading. C++14 mainly consists of technical fixes and tweaks to the current C++11 standard.
However, C++14 also includes some new features, including a class template that simulates dynamic arrays and a core language feature known as runtime-sized arrays with automatic storage duration. Both of these provide mechanisms for creating array-like objects and arrays whose sizes are determined at runtime, very much like C99’s variable length arrays (VLAs). In this article I explain these new C++14 features and how they differ from traditional Standard Library containers and built-in static arrays, respectively.
Before looking at these exciting new features let’s see why traditional C++ containers don’t fit the bill.
In Quest of Dynamic Arrays
Seemingly, you can stick to std::vector
as an alternative to VLAs. In fact, at one time the C++ standards committee considered replacing built-in arrays with vectors all across the board!
Then again, vectors are not arrays. They allocate their objects on the free-store exclusively (whereas arrays’ storage is not restricted to a specific storage class). Second, std::vector
defines the member function resize()
that can change a vector’s size. resize()
violates a crucial array invariant, namely its immutable size. For these reasons, you can’t use std::vector
as a VLA substitute.
At this stage, you might consider using std::array
. std::array
satisfies all of the requirements of a container. As with built-in arrays, it may store its data on the stack or on the free-store. Let’s look at an example of std::array
usage:
#include <array>
#include <iostream>
int main()
{
//allocate on the stack
std::array<int, 10> vals{};
for (int count=0; count<vals.size(); ++count)
vals[count]=count+1;
std::cout<<"the first value is: "
<<vals.front()<<std::endl;
std::cout<<"the last value is: "<<vals[(vals.size()-1)]
<<std::endl;
std::cout<<"the total number of elements is: " <<vals.size()<<std::endl;
int* alias=vals.data();
std::cout<<"the third element is: "<<alias[2]<<std::endl;
}
There is, however, one problem here. std::array supports only static initialization. You can’t specify its size at runtime:
void func(int sz)
{
std::array <int,sz> arr;//sz isn’t a constant expression
}
std::array
may be a reasonable substitute for built-in arrays whose size is known at compile-time. However, if a dynamic array is what you’re after, you need to look elsewhere.
The standard std::valarray
container won’t do either. It supports dynamic initialization. However, it also defines resize()
.
Let’s face it: The C++11 Standard Library doesn’t have a proper substitute for C99’s VLAs. This is why committee members came up with two new proposals. The first proposal introduces a new container class called std::dynarray. The second introduces runtime-sized arrays with automatic storage duration. Both proposals were approved in April 2013 at the Bristol meeting. Let’s look at std::dynarray
first.
To Have and to Hold
A container-based approach offers two advantages:
- Seamless interfacing with standard iterators and algorithms
- Code safety, particularly with respect to range-checking and copying
The newly-added std::dynarray
container provides the familiar interface of a Standard Library container, albeit with two restrictions:
- Once the container is initialized, its size cannot change throughout the container’s lifetime.
- The container object shall not be restricted to the free-store. It may as well reside on the stack – at the discretion of the implementation.
The std::dynarray
constructor takes a parameter indicating the number of elements in the container. There is no default constructor since there is no accepted default size for a dynarray
object. (Note that you cannot instantiate an empty dynarray
and populate it later. However, zero-sized dynarray
objects are permitted.)
Additionally, std::dynarray
supports copy construction. This implies that elements of a dynarray
object must also be copy-constructible. The proposal doesn’t mention move semantics, which is no surprise. Moving from a std::dynarray
might violate its size invariant.
std::dynarray
provides random access iterators and reverse iterators. However, it doesn’t support construction from an initializer_list
nor does it provide a constructor from first
and last
forward iterators. While the last two restrictions might seem limiting, the rationale behind them is as follows: initializer_list
necessarily has a static number of elements. In such cases, a built-in array or a std::array
are probably more appropriate design choices anyway. With respect to construction from first
and last
iterators, the technical consideration is that std::distance(first, last)
is a constant time operation only for random access iterators. Users can calculate this value manually and pass the result to the std::array
constructor.
The proposal introduces a new standard header file called <dynarray>
that defines the class template std::dynarray
. An instance of dynarray<T>
stores elements of type T
contiguously. Thus, if d
is a dynarray<T>
then it obeys the identity &a[n] == &a[0] + n
for all 0 <= n < N
. Note that there is no mechanism for detecting whether a std::dynarray object is allocated on the free-store or on the stack.
Putting std::dynarray to Work
The following function creates a loop that traverses a std::dynarray object and prints its elements. Next, the function copy-constructs a new std::dynarray, sorts it, accumulates its elements’ values and finally, accesses the std::dynarray elements using data(), at() and the [] operator:
#include <dynarray> //C++14
#include <iostream>
#include <algorithm>
void show(const std::dynarray <int>& vals )
{
std::dynarray<int>::const_iterator cit=vals.begin();
for (; cit != vals.end(); cit++)
std::cout << " " << *cit;
std::cout << std::endl;
//runtime initialization of a new dynarray
std::dynarray <int> target (vals);
//algorithms, iterators, data(), & rev iterators
std::sort(target.begin(), target.end());
for (int n=0, *p=target.data(); n<target.size(); n++ )
std::cout << " " << p[n]<<std::endl;
int res=
std::accumulate(target.rbegin(),target.rend(),0);
for (int j=0; j<target.size(); j++) {
//subscript access and at()
target.at(j)=j*2;
std::cout<<" "<<target[j] <<std::endl;
}
std::cout<<"total: "<<res<<std::endl;
}
Notice how similar the interfaces of std::dynarray and std::vector are – so much so that you may replace every occurrence of std::dynarray in the listing above with std::vector without breaking the code or changing its semantics. The only crucial difference between std::dynarray and std::vector in this context is that once you instantiate a std::dynarray object, you cannot change its size.
Runtime-Sized Arrays
Runtime-sized arrays offer the same syntax and performance of C99’s VLAs. The std::dynarray facility, on the other hand, offers the robustness of a first class Standard Library container. Additional differences between these two features include:
- Runtime-sized arrays do not allow zero-sized arrays; std::dynarray does.
- One can use initializer lists with runtime-sized arrays. These aren’t currently supported with std::dynarray.
- There are subtle differences with respect to the exceptions that each feature might throw. I won’t get into the details here but remember always to check the exception specification of each core feature and library class you use.
The following code uses a runtime-sized array. Any C++14 compliant compiler should accept it:
//C++14 only
#include <algorithm>
void f(std::size_t n)
{
int arr[n]; //runtime-sized array
for (std::size_t i=0; i< n; ++i)
arr[i] = i*2;
std::sort(arr, arr+n);
for (std::size_t i=0; i< n; ++i)
std::cout<<" "<<arr[i]<<std::endl;
}
Bear in mind that runtime-sized arrays aren’t precisely the same as C99’s VLAs. The C++14 feature is more restrained, which is just as well. Specifically, the following properties are excluded:
- Runtime-sized multidimensional arrays
- Modifications to the function declarator syntax
- sizeof(a) being a runtime-evaluated expression returning the size of a
- typedef int a[n]; evaluating n and passing it through the typedef
Examples:
void f(std::size_t n)
{
int a[n]; //C++14
unsigned int x=sizeof(a); //ill-formed
const std::type_info& ti = typeid(a); //ill-formed
typedef int t[n]; //ill-formed
}
In Conclusion
Runtime-sized arrays with automatic storage are to std::dynarray as ordinary, statically-sized arrays are to std::array. If you want a low-level, efficient core language arrays whose size is determined at runtime, use them. If however you need a full-blown container that simulates runtime-sized arrays, use std::dynarray instead.
With respect to compiler support of runtime-sized arrays, gcc, Intel C++, and Clang already implement this feature. At present I am not aware of any implementation that supports std::dynarray. However, after its recent approval, vendors should support it soon.
Danny Kalev is a certified system analyst and software engineer specializing in C++. Kalev has written several C++ textbooks and contributes C++ content regularly on various software developers' sites. He was a member of the C++ standards committee and has a master's degree in general linguistics. He’s now pursuing a PhD in linguistics. Follow Danny on Twitter.
See also: