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.