Less Verbose Way to Declare Multidimensional Std::Array

less verbose way to declare multidimensional std::array

When nested, std::array can become very hard to read and unnecessarily verbose. The opposite ordering of the dimensions can be especially confusing.

For example:

std::array < std::array <int, 3 > , 5 > arr1; 

compared to

char c_arr [5][3]; 

Also, note that begin(), end() and size() all return meaningless values when you nest std::array.

For these reasons I've created my own fixed size multidimensional array containers, array_2d and array_3d. They have the advantage that they work with C++98.

They are analogous to std::array but for multidimensional arrays of 2 and 3 dimensions. They are safer and have no worse performance than built-in multidimensional arrays. I didn't include a container for multidimensional arrays with dimensions greater than 3 as they are uncommon. In C++11 a variadic template version could be made which supports an arbitrary number of dimensions (Something like Michael Price's example).

An example of the two-dimensional variant:

//Create an array 3 x 5 (Notice the extra pair of braces) 
fsma::array_2d <double, 3, 5> my2darr = {{
{ 32.19, 47.29, 31.99, 19.11, 11.19},
{ 11.29, 22.49, 33.47, 17.29, 5.01 },
{ 41.97, 22.09, 9.76, 22.55, 6.22 }
}};

Full documentation is available here:
http://fsma.googlecode.com/files/fsma.html

You can download the library here:
http://fsma.googlecode.com/files/fsma.zip

Multidimensional array with dynamic extents (without raw pointers)

Here are a few techniques that I think you could use:

Variadic constructor argument

Rather than having a separate constructor for each possible dimension, you could use variadic argument techniques to create a generic N-dimensional constructor. Something that is your friend here: boost::extents is not required for the constructor argument, but instead anything that meets the requirements of a Collection. One example is just a plain STL or boost array:

template <typename... DimSizes>
PixMat(DimSizes&&... sizes)
: pixMat(boost::array<std::size_t, sizeof...(DimSizes)>{{ static_cast<size_t>(sizes)...}}) {
}

This isn't the most polished implementation; in particular it doesn't place much of a requirement on DimSizes, which should really all be the same unsigned integer type (see this question for possible improvements). Also (for simplicity) perfect forwarding isn't implemented, but that probably just requires wrapping sizes with std::forward<DimSizes>(sizes) in the constructor.
You can consult this stackoverflow post for possible alternative implementations.

Static assertion / SFINAE

Your template base class has a 2D constructor and 3D constructor --- or if you follow the above, a template N-dimensional constructor --- regardless of the value of the actual template parameter. You could use static assertion or SFINAE so that only the the Dims-D dimensional constructor is compilable. This will convert run-time bugs into compilation errors:

template <typename... DimSizes>
PixMat(DimSizes&&... sizes)
: pixMat(boost::array<std::size_t, sizeof...(DimSizes)>{{ static_cast<size_t>(sizes)...}}) {
static_assert(sizeof...(DimSizes) == Dims);
}

Dimension sizes as templates

I think this is a possible though completely orthogonal solution. It's implementation would follow from the above without too much work, but to make this interoperable with the constructor argument based solution would require a lot of hard work (i.e. to make them part of the same class or class hierarchy).

Other libraries

You might want to take a look at Eigen, for example. It does a lot of the aforementioned hard work.

c++ performing operations on functions and declaring functions

For example the function can be defined the following way

const size_t N = 3;

void display( const int ( &MatA )[N][N] )
{
for ( size_t i = 0; i < N; i++ )
{
for ( size_t j = 0; j < N; j++ ) std::cout << MatA[i][j] << " ";
std::cout << std::endl;
}
}

The other way is the following

const size_t N = 3;

void display( const int ( *MatA )[N], size_t n )
{
for ( size_t i = 0; i < n; i++ )
{
for ( size_t j = 0; j < N; j++ ) std::cout << MatA[i][j] << " ";
std::cout << std::endl;
}
}

The functions can be called as

#include <iostream>

const size_t N = 3;

// the function definitions

int main()
{
int a[N][N] = {};
// some code to fill the matrix

display( a );
display( a, N );
}

And at last you can use the approach suggested in comments to the post by @boycy though as for me then I do not like this approach.
For example

#include <iostream>

const size_t N = 3;

void display( const int **MatA, size_t m, size_t n )
{
for ( size_t i = 0; i < m * n; i++ )
{
std::cout << MatA[i] << " ";
if ( ( i + 1 ) % n == 0 ) std::cout << std::endl;
}
}

int main()
{
int a[N][N] = {};
// some code to fill the matrix

display( reinterpret_cast<const int **>( a ), N, N );
}

Now that we have std::array what uses are left for C-style arrays?

Unless I've missed something (I've not followed the most recent changes in the standard too closely), most of the uses of C style arrays still remain. std::array does allow static initialization, but it still won't count the initializers for you. And since the only real use of C style arrays before std::array was for statically initialized tables
along the lines of:

MyStruct const table[] =
{
{ something1, otherthing1 },
// ...
};

using the usual begin and end template functions (adopted in
C++11) to iterate over them. Without ever mentionning the size, which the compiler determines from the number of initializers.

EDIT: Another thing I forgot: string literals are still C style arrays; i.e. with type char[]. I don't think that anyone would exclude using string literals just because we have std::array.

PHP: Cleanest way to modify multidimensional array?

It is possible to do what you want.

This example is largly inspired by Zend_Config and the example given in the PHP docs on the ArrayAccess interface.

edit:
With one minor caveat: you need to call toArray() on data representing an array, to convert it to an array, as the class internally needs to covert array data to an instance of itself, to allow access with the object property operator ->:

Eh, that's not really necessary anymore of course, since it implements ArrayAccess now. ;-)

/edit

class Config
implements ArrayAccess
{
protected $_data;

public function __construct( array $data )
{
foreach( $data as $key => $value )
{
$this->$key = $value;
}
}

public function __get( $key )
{
return $this->offsetGet( $key );
}

public function __isset( $key )
{
return $this->offsetExists( $key );
}

public function __set( $key, $value )
{
$this->offsetSet( $key, $value );
}

public function __unset( $key )
{
$this->offsetUnset( $key );
}

public function offsetSet( $offset, $value )
{
$value = is_array( $value ) ? new self( $value ) : $value;

if( is_null( $offset ) )
{
$this->_data[] = $value;
}
else
{
$this->_data[ $offset ] = $value;
}
}

public function offsetExists( $offset )
{
return isset( $this->_data[ $offset ] );
}

public function offsetUnset( $offset )
{
unset( $this->_data[ $offset ] );
}

public function offsetGet( $offset )
{
return isset( $this->_data[ $offset ] ) ? $this->_data[ $offset ] : null;
}

public function toArray()
{
$array = array();
$data = $this->_data;
foreach( $data as $key => $value )
{
if( $value instanceof Config )
{
$array[ $key ] = $value->toArray();
}
else
{
$array[ $key ] = $value;
}
}
return $array;
}
}

edit 2:
The Config class can even be greatly simplified by extending ArrayObject. As an added benefit, you can cast it to a proper array also.

class Config
extends ArrayObject
{
protected $_data;

public function __construct( array $data )
{
parent::__construct( array(), self::ARRAY_AS_PROPS );
foreach( $data as $key => $value )
{
$this->$key = $value;
}
}

public function offsetSet( $offset, $value )
{
$value = is_array( $value ) ? new self( $value ) : $value;

return parent::offsetSet( $offset, $value );
}
}

Example usage:

$configData = array(
'some' => array(
'deeply' => array(
'nested' => array(
'array' => array(
'some',
'data',
'here'
)
)
)
)
);
$config = new Config( $configData );
// casting to real array
var_dump( (array) $config->some->deeply->nested->array );

$config->some->deeply->nested->array = array( 'new', 'awsome', 'data', 'here' );
// Config object, but still accessible as array
var_dump( $config->some->deeply->nested->array[ 0 ] );

$config[ 'some' ][ 'deeply' ][ 'nested' ][ 'array' ] = array( 'yet', 'more', 'new', 'awsome', 'data', 'here' );
var_dump( $config[ 'some' ][ 'deeply' ][ 'nested' ][ 'array' ] );

$config[ 'some' ][ 'deeply' ][ 'nested' ][ 'array' ][] = 'append data';
var_dump( $config[ 'some' ][ 'deeply' ][ 'nested' ][ 'array' ] );

var_dump( isset( $config[ 'some' ][ 'deeply' ][ 'nested' ][ 'array' ] ) );

unset( $config[ 'some' ][ 'deeply' ][ 'nested' ][ 'array' ] );
var_dump( isset( $config[ 'some' ][ 'deeply' ][ 'nested' ][ 'array' ] ) );

// etc...


Related Topics



Leave a reply



Submit