Read-only array_view/array in C++ AMP – Part 1 of 2
concurrency::array_view and concurrency::array are the most common vehicles for reading and writing data collections in your C++ AMP code. Often in your C++ AMP kernels or parts of your host code, some of the referenced data collections are purely inputs to the computation and are only read-from (never written-to) in that part of the code. This is the first post in a two part series, where we will look at how you can express the read-only nature of accesses for array_view/array input data collections in your C++ AMP code. In the second part, I will talk about why it is highly advisable to do so.
concurrency::array_view<const value_type, rank>
A concurrency::array_view object abstractly denotes a reference to an underlying source data container allocated on the host or an accelerator. Whether an array_view instance has read-only or read-write access to the underlying data is dictated by whether the value_type template argument of the array_view type is const qualified. For example, an array_view<const int> object denotes a read-only linear view of a dense collection of integers and can only be used to read the underlying data. An array_view<float, 2> object on the other hand, denotes a read-write 2D view of a dense collection of floats and can be used to both read-from and write-to the underlying data collection.
int *ptr1 = new int[size];
int *ptr2 = new int[size];
array_view<const int> roArrView(size, ptr1);
// An object of type array_view<const T> can only be read-from
int firstElement = roArrView[0];
// Compilation error: The underlying data cannot be modified through
// an array_view<const T> object
roArrView[0] = 5; // ERROR
// An array_view<const T> object can be assigned to, to point
// to different underlying data source
roArrView = array_view<const int>(size, ptr2);
So if array_view<const value_type> denotes a read-only view of data, what does const array_view<value_type> denote? Well, it denotes the constness of the array_view object itself; i.e. whether the array_view object itself can be modified to reference a different underlying container (or even a different section of its current underlying container) than what it currently references. In other words, the assignment operator cannot be used on a const array_view<T> object to have it point to different underlying data than it currently does. As mentioned earlier, an array_view object just denotes a reference to an underlying data source and using the assignment operator on an array_view object just results in the array_view dropping the reference to its existing data source and start referencing a new data source (underlying the array_view object on the right-hand-side of the assignment operator).
int *ptr1 = new int[size];
int *ptr2 = new int[size];
const array_view<int> constArrView(size, ptr1);
// A const array_view<T> object can both read-from and write-to the
// underlying data
constArrView[0] = constArrView[1] + 1;
// Compilation error: A const array_view<T> object cannot be modified to
// point to different underlying data
constArrView = array_view<int>(size, ptr2); // ERROR
const array_view<const int> roConstArrView(size, ptr1);
// A const array_view<const T> object can only be used to read the
// underlying data
int firstElement = roConstArrView[0];
// Compilation error: a const array_view<const T> object cannot be used to
// modify the underlying data
roConstArrView[0] = 4; // ERROR
// Compilation error: a const array_view<const T> object cannot be modified to
// point to different underlying data
roConstArrView = array_view<const int>(size, ptr2); // ERROR
This may be easier understood through an analogy with pointers. A pointer to const T (const T* ptr) denotes that the data pointed to (by the pointer) is constant and cannot be modified through the pointer. Analogously, array_view<const T> denotes that the referenced underlying data is constant (or read-only) and cannot be modified through the array_view object. On the other hand, a const pointer to T (T* const ptr) denotes that the pointer variable is constant and cannot be assigned a different address than what it currently points to. However, modifying the referenced data through the pointer is perfectly ok. Analogously, a const array_view<T> object denotes a constant array_view object and cannot be modified (using the assignment operator) to point to different underlying data than what it currently references. But the array_view has read-write access and can be used to modify the referenced data.
array_view type |
Analogous pointer type |
array_view<const T> |
const T* |
const array_view<T> |
T* const |
const array_view<const T> |
const T* const |
Finally, as you would expect, a read-only array_view (array_view<const T> ) object can be freely constructed from a read-write array_view (array_view<T> ) object. But constructing a read-write array_view from a read-only array_view would compromise the source array_view’s read-only restriction on the referenced data, and this unsafe operation is naturally disallowed.
int *ptr1 = new int[size];
const int *ptr2 = new int[size];
// An array_view<const T> object can be constructed from a writable data source
array_view<const int> roArrView(size, ptr1);
array_view<int> rwArrView(size, ptr1);
// A array_view<const T> object can be freely constructed from an
// array_view<T> object
array_view<const int> roArrView1 = rwArrView;
parallel_for_each(roArrView1.extent, [=](index<1> idx) restrict(amp) {
// The object roArrView is of type array_view<const int> and can only
// be used to read the source data
int temp = roArrView[idx];
// Compilation error: Modifying the underlying data through
// a array_view<const T> object is disallowed
roArrView[idx] = temp + 5; // ERROR
});
// Compilation error: Cannot construct a array_view<T> over
// a read-only data source (const int* ptr2)
array_view rwArrView1(size, ptr2); // ERROR
// Compilation error: Cannot construct a array_view<T> object from
// a array_view<const T> object
array_view rwArrView2(roArrView); // ERROR
const array<value_type, rank>
A concurrency::array object denotes a dense multi-dimensional data container allocated on a specific accelerator_view of a C++ AMP accelerator. Whether an array object is read-only or read-write is dictated by whether the array object or the C++ reference through which it is accessed is const qualified. For example, a const array<int> object denotes a read-only linear dense container of integers while an array<float, 2> object denotes a 2D dense container of floats. And if you are wondering what array<const T> denotes – it denotes nothing, is disallowed and will result in a compilation error.
Similar to array_view, the read-only restriction can be freely attributed by creating a const array<T> reference to an array<T> object. But, dropping the read-only restriction is unsafe (such as creating a non-const array<T> reference from a const array<T> object) and is disallowed per the normal C++ constness rules. It is worth noting here that an array object denotes the data container itself unlike an array_view object which denotes a reference to an underlying data source. Hence copy constructing an array objector assigning to an array object from another array object results in construction of new container with a deep copy of the source array contents unlike array_view copy construction or assignment which just results in the destination array_view to point to the same underlying data source as the source array_view object it is constructed or assigned from.
int *ptr1 = new int[size];
// An array denotes a data container and the contents of the source ptr1
// are copied to the newly allocated memory
array<int> rwArr(size, ptr1);
// A const array<T> object denotes a read-only array
const array<float> roArr(size, ptr1);
// Read-onliness can be attributed by creating const reference
// to a read-write array<T> object
const array<int>& roArrRef = rwArr;
parallel_for_each(roArrRef.extent, [&](index<1> idx) restrict(amp) {
// Compilation error: roArrRef being a const array<int>& is read-only and
// cannot be used to modify the array contents
roArrRef[idx] = roArrRef[idx] + 5; // ERROR
});
// Compilation error: Constness of an array cannot be dropped
array<float>& rwArrRef = roArr; // ERROR
array<int>& rwArrRef2 = roArrRef; // ERROR
The concurrency::texture type has the same semantics as the array type with regard to constness; i.e. a const texture<T> object denotes read-only access to the texture data.
In closing
Having looked at how to specify the read-only restriction for array_view/array data collections in your C++ AMP code in this part, in the next part I will discuss the benefits of applying the read-only restriction when your array_view/array data collections are only read-from in a C++ AMP kernel or parts of your host code. I would love to hear your thoughts, comments, questions and feedback below or on our MSDN forum.