2d Array C++ Assignments


The Boost Multidimensional Array Library
(Boost.MultiArray)

Synopsis

The Boost Multidimensional Array Library provides a class template for multidimensional arrays, as well as semantically equivalent adaptors for arrays of contiguous data. The classes in this library implement a common interface, formalized as a generic programming concept. The interface design is in line with the precedent set by the C++ Standard Library containers. Boost MultiArray is a more efficient and convenient way to express N-dimensional arrays than existing alternatives (especially the formulation of N-dimensional arrays). The arrays provided by the library may be accessed using the familiar syntax of native C++ arrays. Additional features, such as resizing, reshaping, and creating views are available (and described below).

Table of Contents

  1. Introduction
  2. Short Example
  3. MultiArray Components
  4. Construction and Assignment
  5. Array View and Subarray Type Generators
  6. Specifying Array Dimensions
  7. Accessing Elements
  8. Creating Views
  9. Storage Ordering
  10. Setting the Array Base
  11. Changing an Array's Shape
  12. Resizing an Array
  13. MultiArray Concept
  14. Test Cases
  15. Related Work
  16. Credits

Introduction

The C++ standard library provides several generic containers, but it does not provide any multidimensional array types. The class template can be used to implement N-dimensional arrays, for example expressing a 2-dimensional array of elements using the type , but the resulting interface is unwieldy and the memory overhead can be quite high. Native C++ arrays (i.e. ) do not immediately interoperate well with the C++ Standard Library, and they also lose information at function call boundaries (specifically the extent of the last dimension). Finally, a dynamically allocated contiguous block of elements can be treated as an array, though this method requires manual bookkeeping that is error prone and obfuscates the intent of the programmer.

The Boost MultiArray library enhances the C++ standard containers with versatile multi-dimensional array abstractions. It includes a general array class template and native array adaptors that support idiomatic array operations and interoperate with C++ Standard Library containers and algorithms. The arrays share a common interface, expressed as a generic programming in terms of which generic array algorithms can be implemented.

This document is meant to provide an introductory tutorial and user's guide for the most basic and common usage patterns of MultiArray components. The reference manual provides more complete and formal documentation of library features.

Short Example

What follows is a brief example of the use of :
#include "boost/multi_array.hpp" #include <cassert> int main () { // Create a 3D array that is 3 x 4 x 2 typedef boost::multi_array<double, 3> array_type; typedef array_type::index index; array_type A(boost::extents[3][4][2]); // Assign values to the elements int values = 0; for(index i = 0; i != 3; ++i) for(index j = 0; j != 4; ++j) for(index k = 0; k != 2; ++k) A[i][j][k] = values++; // Verify values int verify = 0; for(index i = 0; i != 3; ++i) for(index j = 0; j != 4; ++j) for(index k = 0; k != 2; ++k) assert(A[i][j][k] == verify++); return 0; }

MultiArray Components

Boost.MultiArray's implementation (boost/multi_array.hpp) provides three user-level class templates:
  1. ,
  2. , and
is a container template. When instantiated, it allocates space for the number of elements corresponding to the dimensions specified at construction time. A may also be default constructed and resized as needed.

adapts an existing array of data to provide the interface. does not own the data passed to it.

is similar to but guarantees that the contents of the array are immutable. It can thus wrap pointers of type T const*.

The three components exhibit very similar behavior. Aside from constructor parameters, and export the same interface. provides only the constness-preserving portions of the interface.

Construction and Assignment

Each of the array types - , , and - provides a specialized set of constructors. For further information, consult their reference pages.

All of the non-const array types in this library provide assignment operators. Each of the array types , , , and can be assigned from any of the others, so long as their shapes match. The const variants, , , and , can be the source of a copy to an array with matching shape. Assignment results in a deep (element by element) copy of the data contained within an array.

Array View and Subarray Type Generators

In some situations, the use of nested generators for array_view and subarray types is inconvenient. For example, inside a function template parameterized upon array type, the extra "template" keywords can be obfuscating. More likely though, some compilers cannot handle templates nested within template parameters. For this reason the type generators, , , , and are provided. Thus, the two typedefs in the following example result in the same type:
template <typename Array> void my_function() { typedef typename Array::template array_view<3>::type view1_t; typedef typename boost::array_view_gen<Array,3>::type view2_t; // ... }

Specifying Array Dimensions

When creating most of the Boost.MultiArray components, it is necessary to specify both the number of dimensions and the extent of each ( also provides a default constructor). Though the number of dimensions is always specified as a template parameter, two separate mechanisms have been provided to specify the extent of each.

The first method involves passing a Collection of extents to a constructor, most commonly a . The constructor will retrieve the beginning iterator from the container and retrieve N elements, corresponding to extents for the N dimensions. This is useful for writing dimension-independent code.

Example

typedef boost::multi_array<double, 3> array_type; boost::array<array_type::index, 3> shape = {{ 3, 4, 2 }}; array_type A(shape);

The second method involves passing the constructor an object, specifying the matrix dimensions. The type is defined in the namespace and as a member of every array type, but by default, the library constructs a global object . In case of concern about memory used by these objects, defining before including the library header inhibits its construction.

Example

typedef boost::multi_array<double, 3> array_type; array_type A(boost::extents[3][4][2]);

Accessing Elements

The Boost.MultiArray components provide two ways of accessing specific elements within a container. The first uses the traditional C array notation, provided by .

Example

typedef boost::multi_array<double, 3> array_type; array_type A(boost::extents[3][4][2]); A[0][0][0] = 3.14; assert(A[0][0][0] == 3.14);

The second method involves passing a Collection of indices to . N indices will be retrieved from the Collection for the N dimensions of the container.

Example

typedef boost::multi_array<double, 3> array_type; array_type A(boost::extents[3][4][2]); boost::array<array_type::index,3> idx = {{0,0,0}}; A(idx) = 3.14; assert(A(idx) == 3.14);
This can be useful for writing dimension-independent code, and under some compilers may yield higher performance than

By default, both of the above element access methods perform range checking. If a supplied index is out of the range defined for an array, an assertion will abort the program. To disable range checking (for performance reasons in production releases), define the preprocessor macro prior to including multi_array.hpp in your application.

Creating Views

Boost.MultiArray provides the facilities for creating a sub-view of an already existing array component. It allows you to create a sub-view that retains the same number of dimensions as the original array or one that has less dimensions than the original as well.

Sub-view creation occurs by placing a call to operator[], passing it an type. The is populated by passing objects to its . The and types are defined in the namespace and as nested members of every array type. Similar to , the library by default constructs the object . You can suppress this object by defining before including the library header. A simple sub-view creation example follows.

Example

// myarray = 2 x 3 x 4 // // array_view dims: [base,bound) (dimension striding default = 1) // dim 0: [0,2) // dim 1: [1,3) // dim 2: [0,4) (strided by 2), // typedef boost::multi_array_types::index_range range; // OR typedef array_type::index_range range; array_type::array_view<3>::type myview = myarray[ boost::indices[range(0,2)][range(1,3)][range(0,4,2)] ]; for (array_type::index i = 0; i != 2; ++i) for (array_type::index j = 0; j != 2; ++j) for (array_type::index k = 0; k != 2; ++k) assert(myview[i][j][k] == myarray[i][j+1][k*2]);

By passing an integral value to the index_gen, one may create a subview with fewer dimensions than the original array component (also called slicing).

Example

// myarray = 2 x 3 x 4 // // array_view dims: // [base,stride,bound) // [0,1,2), 1, [0,2,4) // typedef boost::multi_array_types::index_range range; array_type::index_gen indices; array_type::array_view<2>::type myview = myarray[ indices[range(0,2)][1][range(0,4,2)] ]; for (array_type::index i = 0; i != 2; ++i) for (array_type::index j = 0; j != 2; ++j) assert(myview[i][j] == myarray[i][1][j*2]);

More on

The type provides several methods of specifying ranges for subview generation. Here are a few range instantiations that specify the same range.

Example

// [base,stride,bound) // [0,2,4) typedef boost::multi_array_types::index_range range; range a_range; a_range = range(0,4,2); a_range = range().start(0).finish(4).stride(2); a_range = range().start(0).stride(2).finish(4); a_range = 0 <= range().stride(2) < 4; a_range = 0 <= range().stride(2) <= 3;
An object passed to a slicing operation will inherit its start and/or finish value from the array being sliced if you do not supply one. This conveniently prevents you from having to know the bounds of the array dimension in certain cases. For example, the default-constructed range will take the full extent of the dimension it is used to specify.

Example

typedef boost::multi_array_types::index_range range; range a_range; // All elements in this dimension a_range = range(); // indices i where 3 <= i a_range = range().start(3) a_range = 3 <= range(); a_range = 2 < range(); // indices i where i < 7 a_range = range().finish(7) a_range = range() < 7; a_range = range() <= 6;
The following example slicing operations exhibit some of the alternatives shown above
// take all of dimension 1 // take i < 5 for dimension 2 // take 4 <= j <= 7 for dimension 3 with stride 2 myarray[ boost::indices[range()][range() < 5 ][4 <= range().stride(2) <= 7] ];

Storage Ordering

Each array class provides constructors that accept a storage ordering parameter. This is most useful when interfacing with legacy codes that require an ordering different from standard C, such as FORTRAN. The possibilities are , , and .

, which is the default, will store elements in memory in the same order as a C array would, that is, the dimensions are stored from last to first.

will store elements in memory in the same order as FORTRAN would: from the first dimension to the last. Note that with use of this parameter, the array indices will remain zero-based.

Example

typedef boost::multi_array<double,3> array_type; array_type A(boost::extents[3][4][2],boost::fortran_storage_order()); call_fortran_function(A.data());

allows one to customize both the order in which dimensions are stored in memory and whether dimensions are stored in ascending or descending order.

Example

typedef boost::general_storage_order<3> storage; typedef boost::multi_array<int,3> array_type; // Store last dimension, then first, then middle array_type::size_type ordering[] = {2,0,1}; // Store the first dimension(dimension 0) in descending order bool ascending[] = {false,true,true}; array_type A(extents[3][4][2],storage(ordering,ascending));

Setting The Array Base

In some situations, it may be inconvenient or awkward to use an array that is zero-based. the Boost.MultiArray components provide two facilities for changing the bases of an array. One may specify a pair of range values, with the type, to the constructor in order to set the base value.

Example

typedef boost::multi_array<double, 3> array_type; typedef boost::multi_array_types::extent_range range; // OR typedef array_type::extent_range range; array_type::extent_gen extents; // dimension 0: 0-based // dimension 1: 1-based // dimension 2: -1 - based array_type A(extents[2][range(1,4)][range(-1,3)]);

An alternative is to first construct the array normally then reset the bases. To set all bases to the same value, use the member function, passing it a single new index value.

Example

typedef boost::multi_array<double, 3> array_type; array_type::extent_gen extents; array_type A(extents[2][3][4]); // change to 1-based A.reindex(1)

An alternative is to set each base separately using the member function, passing it a Collection of index bases.

Example

typedef boost::multi_array<double, 3> array_type; array_type::extent_gen extents; // dimension 0: 0-based // dimension 1: 1-based // dimension 2: (-1)-based array_type A(extents[2][3][4]); boost::array<array_type::index,ndims> bases = {{0, 1, -1}}; A.reindex(bases);

Changing an Array's Shape

The Boost.MultiArray arrays provide a reshape operation. While the number of dimensions must remain the same, the shape of the array may change so long as the total number of elements contained remains the same.

Example

typedef boost::multi_array<double, 3> array_type; array_type::extent_gen extents; array_type A(extents[2][3][4]); boost::array<array_type::index,ndims> dims = {{4, 3, 2}}; A.reshape(dims);

Note that reshaping an array does not affect the indexing.

Resizing an Array

The class provides an element-preserving resize operation. The number of dimensions must remain the same, but the extent of each dimension may be increased and decreased as desired. When an array is made strictly larger, the existing elements will be preserved by copying them into the new underlying memory and subsequently destructing the elements in the old underlying memory. Any new elements in the array are default constructed. However, if the new array size shrinks some of the dimensions, some elements will no longer be available.

Example

typedef boost::multi_array<int, 3> array_type; array_type::extent_gen extents; array_type A(extents[3][3][3]); A[0][0][0] = 4; A[2][2][2] = 5; A.resize(extents[2][3][4]); assert(A[0][0][0] == 4); // A[2][2][2] is no longer valid.

MultiArray Concept

Boost.MultiArray defines and uses the MultiArray concept. It specifies an interface for N-dimensional containers.

Test Cases

Boost.MultiArray comes with a suite of test cases meant to exercise the features and semantics of the library. A description of the test cases can be found here.

Related Work

boost::array and std::vector are one-dimensional containers of user data. Both manage their own memory. is a low-level C++ Standard Library component meant to provide portable high performance for numerical applications. Blitz++ is an array library developed by Todd Veldhuizen. It uses advanced C++ techniques to provide near-Fortran performance for array-based numerical applications. array_traits is a beta library, formerly distributed with Boost, that provides a means to create iterators over native C++ arrays. This library is analogous to boost::array in that it augments C style N-dimensional arrays, as does for C one-dimensional arrays.

Credits

  • Ronald Garcia is the primary author of the library.
  • Jeremy Siek helped with the library and provided a sounding board for ideas, advice, and assistance porting to Microsoft Visual C++.
  • Giovanni Bavestrelli provided an early implementation of an N-dimensional array which inspired feedback from the Boost mailing list members. Some design decisions in this work were based upon this implementation and the comments it elicited.
  • Todd Veldhuizen wrote Blitz++, which inspired some aspects of this design. In addition, he supplied feedback on the design and implementation of the library.
  • Jeremiah Willcock provided feedback on the implementation and design of the library and some suggestions for features.
  • Beman Dawes helped immensely with porting the library to Microsoft Windows compilers.

Ronald Garcia Last modified: Tue Feb 7 17:15:50 EST 2006

Arrays

An array is a series of elements of the same type placed in contiguous memory locations that can be individually referenced by adding an index to a unique identifier.

That means that, for example, five values of type can be declared as an array without having to declare 5 different variables (each with its own identifier). Instead, using an array, the five values are stored in contiguous memory locations, and all five can be accessed using the same identifier, with the proper index.

For example, an array containing 5 integer values of type called could be represented as:


where each blank panel represents an element of the array. In this case, these are values of type . These elements are numbered from 0 to 4, being 0 the first and 4 the last; In C++, the first element in an array is always numbered with a zero (not a one), no matter its length.

Like a regular variable, an array must be declared before it is used. A typical declaration for an array in C++ is:


where is a valid type (such as , ...), is a valid identifier and the field (which is always enclosed in square brackets ), specifies the length of the array in terms of the number of elements.

Therefore, the array, with five elements of type , can be declared as:



NOTE: The field within square brackets , representing the number of elements in the array, must be a constant expression, since arrays are blocks of static memory whose size must be determined at compile time, before the program runs.

Initializing arrays

By default, regular arrays of local scope (for example, those declared within a function) are left uninitialized. This means that none of its elements are set to any particular value; their contents are undetermined at the point the array is declared.

But the elements in an array can be explicitly initialized to specific values when it is declared, by enclosing those initial values in braces {}. For example:



This statement declares an array that can be represented like this:


The number of values between braces shall not be greater than the number of elements in the array. For example, in the example above, was declared having 5 elements (as specified by the number enclosed in square brackets, ), and the braces contained exactly 5 values, one for each element. If declared with less, the remaining elements are set to their default values (which for fundamental types, means they are filled with zeroes). For example:



Will create an array like this:


The initializer can even have no values, just the braces:



This creates an array of five values, each initialized with a value of zero:


When an initialization of values is provided for an array, C++ allows the possibility of leaving the square brackets empty . In this case, the compiler will assume automatically a size for the array that matches the number of values included between the braces :



After this declaration, array would be 5 long, since we have provided 5 initialization values.

Finally, the evolution of C++ has led to the adoption of universal initialization also for arrays. Therefore, there is no longer need for the equal sign between the declaration and the initializer. Both these statements are equivalent:



Static arrays, and those declared directly in a namespace (outside any function), are always initialized. If no explicit initializer is specified, all the elements are default-initialized (with zeroes, for fundamental types).

Accessing the values of an array

The values of any of the elements in an array can be accessed just like the value of a regular variable of the same type. The syntax is:


Following the previous examples in which had 5 elements and each of those elements was of type , the name which can be used to refer to each element is the following:


For example, the following statement stores the value 75 in the third element of :



and, for example, the following copies the value of the third element of to a variable called :



Therefore, the expression is itself a variable of type .

Notice that the third element of is specified , since the first one is , the second one is , and therefore, the third one is . By this same reason, its last element is . Therefore, if we write , we would be accessing the sixth element of , and therefore actually exceeding the size of the array.

In C++, it is syntactically correct to exceed the valid range of indices for an array. This can create problems, since accessing out-of-range elements do not cause errors on compilation, but can cause errors on runtime. The reason for this being allowed will be seen in a later chapter when pointers are introduced.

At this point, it is important to be able to clearly distinguish between the two uses that brackets have related to arrays. They perform two different tasks: one is to specify the size of arrays when they are declared; and the second one is to specify indices for concrete array elements when they are accessed. Do not confuse these two possible uses of brackets with arrays.



The main difference is that the declaration is preceded by the type of the elements, while the access is not.

Some other valid operations with arrays:



For example:



Multidimensional arrays

Multidimensional arrays can be described as "arrays of arrays". For example, a bidimensional array can be imagined as a two-dimensional table made of elements, all of them of a same uniform data type.


represents a bidimensional array of 3 per 5 elements of type . The C++ syntax for this is:



and, for example, the way to reference the second element vertically and fourth horizontally in an expression would be:




(remember that array indices always begin with zero).

Multidimensional arrays are not limited to two indices (i.e., two dimensions). They can contain as many indices as needed. Although be careful: the amount of memory needed for an array increases exponentially with each dimension. For example:



declares an array with an element of type for each second in a century. This amounts to more than 3 billion ! So this declaration would consume more than 3 gigabytes of memory!

At the end, multidimensional arrays are just an abstraction for programmers, since the same results can be achieved with a simple array, by multiplying its indices:



With the only difference that with multidimensional arrays, the compiler automatically remembers the depth of each imaginary dimension. The following two pieces of code produce the exact same result, but one uses a bidimensional array while the other uses a simple array:

multidimensional arraypseudo-multidimensional array

None of the two code snippets above produce any output on the screen, but both assign values to the memory block called jimmy in the following way:


Note that the code uses defined constants for the width and height, instead of using directly their numerical values. This gives the code a better readability, and allows changes in the code to be made easily in one place.

Arrays as parameters

At some point, we may need to pass an array to a function as a parameter. In C++, it is not possible to pass the entire block of memory represented by an array to a function directly as an argument. But what can be passed instead is its address. In practice, this has almost the same effect, and it is a much faster and more efficient operation.

To accept an array as parameter for a function, the parameters can be declared as the array type, but with empty brackets, omitting the actual size of the array. For example:



This function accepts a parameter of type "array of " called . In order to pass to this function an array declared as:



it would be enough to write a call like this:



Here you have a complete example:



In the code above, the first parameter () accepts any array whose elements are of type , whatever its length. For that reason, we have included a second parameter that tells the function the length of each array that we pass to it as its first parameter. This allows the for loop that prints out the array to know the range to iterate in the array passed, without going out of range.

In a function declaration, it is also possible to include multidimensional arrays. The format for a tridimensional array parameter is:



For example, a function with a multidimensional array as argument could be:



Notice that the first brackets are left empty, while the following ones specify sizes for their respective dimensions. This is necessary in order for the compiler to be able to determine the depth of each additional dimension.

In a way, passing an array as argument always loses a dimension. The reason behind is that, for historical reasons, arrays cannot be directly copied, and thus what is really passed is a pointer. This is a common source of errors for novice programmers. Although a clear understanding of pointers, explained in a coming chapter, helps a lot.

Library arrays

The arrays explained above are directly implemented as a language feature, inherited from the C language. They are a great feature, but by restricting its copy and easily decay into pointers, they probably suffer from an excess of optimization.

To overcome some of these issues with language built-in arrays, C++ provides an alternative array type as a standard container. It is a type template (a class template, in fact) defined in header .

Containers are a library feature that falls out of the scope of this tutorial, and thus the class will not be explained in detail here. Suffice it to say that they operate in a similar way to built-in arrays, except that they allow being copied (an actually expensive operation that copies the entire block of memory, and thus to use with care) and decay into pointers only when explicitly told to do so (by means of its member ).

Just as an example, these are two versions of the same example using the language built-in array described in this chapter, and the container in the library:

language built-in arraycontainer library array

As you can see, both kinds of arrays use the same syntax to access its elements: . Other than that, the main differences lay on the declaration of the array, and the inclusion of an additional header for the library array. Notice also how it is easy to access the size of the library array.

0 Replies to “2d Array C++ Assignments”

Lascia un Commento

L'indirizzo email non verrà pubblicato. I campi obbligatori sono contrassegnati *