A minimalistic smart array class template

L

legends2k

Hi,
I've writing a container class as a replacement for raw arrays; just
called it smart_array (bare with me if it's dumb), which has the
niceties of a vector, can be used for dynamic memory requirements and
cleans up itself safely. When I showed it to my friends for a review
and to point out flaws & possible bugs in some peculiar use case; they
said I could post it here to get it reviewed/constructively criticized
by many C++ stalwarts. So am presenting it here:

smart_array.h :

#include <vector>
#include <cassert>
#include <cstdarg>
using std::vector;

template <typename T, size_t N>
class smart_array
{
vector < smart_array<T, N - 1> > vec;

public:
explicit smart_array(vector <size_t> &dimensions)
{
assert(N == dimensions.size());

vector <size_t>::iterator it = ++dimensions.begin();
vector <size_t> dimensions_remaining(it, dimensions.end());

smart_array <T, N - 1> temp_smart_array(dimensions_remaining);
vec.assign(dimensions[0], temp_smart_array);
}

explicit smart_array(size_t dimension_1 = 1, ...)
{
static_assert(N > 0, "Error: smart_array expects 1 or more
dimension(s)");
assert(dimension_1 > 1);

va_list dim_list;
vector <size_t> dimensions_remaining(N - 1);

va_start(dim_list, dimension_1);
for(size_t i = 0; i < N - 1; ++i)
{
size_t dimension_n = va_arg(dim_list, size_t);
assert(dimension_n > 0);
dimensions_remaining = dimension_n;
}
va_end(dim_list);

smart_array <T, N - 1> temp_smart_array(dimensions_remaining);
vec.assign(dimension_1, temp_smart_array);
}

smart_array<T, N - 1>& operator[](size_t index)
{
assert(index < vec.size() && index >= 0);
return vec[index];
}

size_t length() const
{
return vec.size();
}
};

template<typename T>
class smart_array<T, 1>
{
vector <T> vec;

public:
explicit smart_array(vector <size_t> &dimension) :
vec(dimension[0])
{
assert(dimension[0] > 0);
}

explicit smart_array(size_t dimension_1 = 1) : vec(dimension_1)
{
assert(dimension_1 > 0);
}

T& operator[](size_t index)
{
assert(index < vec.size() && index >= 0);
return vec[index];
}

size_t length()
{
return vec.size();
}
};

------ End of smart_array.h ------


Sample Usage:

#include "smart_array.h"
#include <iostream>
using std::cout;
using std::endl;

int main()
{
// testing 1 dimension
smart_array <int, 1> x(3);
x[0] = 0, x[1] = 1, x[2] = 2;
cout << "x.length(): " << x.length() << endl;

// testing 2 dimensions
smart_array <float, 2> y(2, 3);
y[0][0] = y[0][1] = y[0][2] = 0;
y[1][0] = y[1][1] = y[1][2] = 1;
cout << "y.length(): " << y.length() << endl;
cout << "y[0].length(): " << y[0].length() << endl;

// testing 3 dimensions
smart_array <char, 3> z(2, 4, 5);
cout << "z.length(): " << z.length() << endl;
cout << "z[0].length(): " << z[0].length() << endl;
cout << "z[0][0].length(): " << z[0][0].length() << endl;
z[0][0][4] = 'c'; cout << z[0][0][4] << endl;

// testing 4 dimensions
smart_array <bool, 4> r(2, 3, 4, 5);
cout << "z.length(): " << r.length() << endl;
cout << "z[0].length(): " << r[0].length() << endl;
cout << "z[0][0].length(): " << r[0][0].length() << endl;
cout << "z[0][0][0].length(): " << r[0][0][0].length() << endl;

// testing copy constructor
smart_array <float, 2> copied_y(y);
cout << "copied_y.length(): " << copied_y.length() << endl;
cout << "copied_y[0].length(): " << copied_y[0].length() << endl;

cout << copied_y[0][0] << "\t" << copied_y[1][0] << "\t" <<
copied_y[0][1] << "\t" <<
copied_y[1][1] << "\t" << copied_y[0][2] << "\t" << copied_y[1]
[2] << endl;

return 0;
}


Thanks for your time!

Regards
Sundaram
 
J

Juha Nieminen

legends2k said:
I've writing a container class as a replacement for raw arrays; just
called it smart_array (bare with me if it's dumb), which has the
niceties of a vector, can be used for dynamic memory requirements and
cleans up itself safely. When I showed it to my friends for a review
and to point out flaws & possible bugs in some peculiar use case; they
said I could post it here to get it reviewed/constructively criticized
by many C++ stalwarts. So am presenting it here:

Am I understanding correctly that basically the only thing this class
is doing, is providing a shortcut so that instead of having to write eg:

std::vector<std::vector<std::vector<int> > > vec3d;

you can write:

smart_array<int, 3> vec3d;

?

(Well, besides providing only operator[] and nothing else that std::vector
provides...)

It raises the question whether being able to declare multi-dimensional
vectors with a slightly shorter syntax is worth throwing away all the
member functions that std::vector offers...
 
L

legends2k

legends2k said:
I've writing a container class as a replacement for raw arrays; just
called it smart_array (bare with me if it's dumb), which has the
niceties of a vector, can be used for dynamic memory requirements and
cleans up itself safely. When I showed it to my friends for a review
and to point out flaws & possible bugs in some peculiar use case; they
said I could post it here to get it reviewed/constructively criticized
by many C++ stalwarts. So am presenting it here:

  Am I understanding correctly that basically the only thing this class
is doing, is providing a shortcut so that instead of having to write eg:

    std::vector<std::vector<std::vector<int> > > vec3d;

you can write:

    smart_array<int, 3> vec3d;

?

  (Well, besides providing only operator[] and nothing else that std::vector
provides...)

  It raises the question whether being able to declare multi-dimensional
vectors with a slightly shorter syntax is worth throwing away all the
member functions that std::vector offers...

Yes, it's has operator[] and size() of vector, other methods of a
vector can be
implemented too. It's closer to an array in a way that all the members
are already
initialized (by the default ctor); while when using std::vector one
has to
call assign. Basically I wrote it for porting Java code to C++, where
you can
directly assign to some arbitrary index without bothering about
push_backs. Nothing
that can't be done with std::vector, it's more of a wrapper to it.
directly access th 10th variable without
 
L

legends2k

legends2k said:
I've writing a container class as a replacement for raw arrays; just
called it smart_array (bare with me if it's dumb), which has the
niceties of a vector, can be used for dynamic memory requirements and
cleans up itself safely. When I showed it to my friends for a review
and to point out flaws & possible bugs in some peculiar use case; they
said I could post it here to get it reviewed/constructively criticized
by many C++ stalwarts. So am presenting it here:

  Am I understanding correctly that basically the only thing this class
is doing, is providing a shortcut so that instead of having to write eg:

    std::vector<std::vector<std::vector<int> > > vec3d;

you can write:

    smart_array<int, 3> vec3d;

?

  (Well, besides providing only operator[] and nothing else that std::vector
provides...)

  It raises the question whether being able to declare multi-dimensional
vectors with a slightly shorter syntax is worth throwing away all the
member functions that std::vector offers...

Yes, it's has operator[] and size() of vector, other methods of a
vector can be implemented too. It's closer to an array in a way that
all the members are already initialized (by the default ctor); while
when using std::vector one has to call assign. Basically I wrote it
for porting Java code to C++, where one can directly assign to some
arbitrary index without bothering about push_backs. Nothing that can't
be done with std::vector, it's more of a wrapper to it.
 
V

Vladimir Jovic

legends2k wrote:

class smart_array
{
vector < smart_array<T, N - 1> > vec;

public:
explicit smart_array(vector <size_t> &dimensions)
{
assert(N == dimensions.size());

vector <size_t>::iterator it = ++dimensions.begin();
vector <size_t> dimensions_remaining(it, dimensions.end());

smart_array <T, N - 1> temp_smart_array(dimensions_remaining);
vec.assign(dimensions[0], temp_smart_array);
}

explicit smart_array(size_t dimension_1 = 1, ...)
{
static_assert(N > 0, "Error: smart_array expects 1 or more
dimension(s)");

Error : static_assert not defined

assert(dimension_1 > 1);
Sample Usage:

#include "smart_array.h"
#include <iostream>
using std::cout;
using std::endl;

int main()
{
// testing 1 dimension
smart_array <int, 1> x(3);
x[0] = 0, x[1] = 1, x[2] = 2;
cout << "x.length(): " << x.length() << endl;

// testing 2 dimensions
smart_array <float, 2> y(2, 3);
y[0][0] = y[0][1] = y[0][2] = 0;
y[1][0] = y[1][1] = y[1][2] = 1;
cout << "y.length(): " << y.length() << endl;
cout << "y[0].length(): " << y[0].length() << endl;

// testing 3 dimensions
smart_array <char, 3> z(2, 4, 5);
cout << "z.length(): " << z.length() << endl;
cout << "z[0].length(): " << z[0].length() << endl;
cout << "z[0][0].length(): " << z[0][0].length() << endl;
z[0][0][4] = 'c'; cout << z[0][0][4] << endl;

// testing 4 dimensions
smart_array <bool, 4> r(2, 3, 4, 5);
cout << "z.length(): " << r.length() << endl;
cout << "z[0].length(): " << r[0].length() << endl;
cout << "z[0][0].length(): " << r[0][0].length() << endl;
cout << "z[0][0][0].length(): " << r[0][0][0].length() << endl;

// testing copy constructor
smart_array <float, 2> copied_y(y);
cout << "copied_y.length(): " << copied_y.length() << endl;
cout << "copied_y[0].length(): " << copied_y[0].length() << endl;


// new test
smart_array <bool, 4> r(2, "ops");
cout << "r.length(): " << r.length() << endl;
cout << "r[0].length(): " << r[0].length() << endl;
cout << "r[0][0].length(): " << r[0][0].length() << endl;
cout << "r[0][0][0].length(): " << r[0][0][0].length() << endl;

cout << copied_y[0][0] << "\t" << copied_y[1][0] << "\t" <<
copied_y[0][1] << "\t" <<
copied_y[1][1] << "\t" << copied_y[0][2] << "\t" << copied_y[1]
[2] << endl;

return 0;
}


Output of program with the new test :
Hello world!
 
L

legends2k

legends2k wrote:

<snip>


class smart_array
{
    vector < smart_array<T, N - 1> > vec;
public:
    explicit smart_array(vector <size_t> &dimensions)
    {
        assert(N == dimensions.size());
        vector <size_t>::iterator it = ++dimensions.begin();
        vector <size_t> dimensions_remaining(it, dimensions.end());
        smart_array <T, N - 1> temp_smart_array(dimensions_remaining);
        vec.assign(dimensions[0], temp_smart_array);
    }
    explicit smart_array(size_t dimension_1 = 1, ...)
    {
        static_assert(N > 0, "Error: smart_array expects 1 or more
dimension(s)");

Error : static_assert not defined


        assert(dimension_1 > 1);
Sample Usage:
#include "smart_array.h"
#include <iostream>
using std::cout;
using std::endl;
int main()
{
    // testing 1 dimension
    smart_array <int, 1> x(3);
    x[0] = 0, x[1] = 1, x[2] = 2;
    cout << "x.length(): " << x.length() << endl;
    // testing 2 dimensions
    smart_array <float, 2> y(2, 3);
    y[0][0] = y[0][1] = y[0][2] = 0;
    y[1][0] = y[1][1] = y[1][2] = 1;
    cout << "y.length(): " << y.length() << endl;
    cout << "y[0].length(): " << y[0].length() << endl;
    // testing 3 dimensions
    smart_array <char, 3> z(2, 4, 5);
    cout << "z.length(): " << z.length() << endl;
    cout << "z[0].length(): " << z[0].length() << endl;
    cout << "z[0][0].length(): " << z[0][0].length() << endl;
    z[0][0][4] = 'c'; cout << z[0][0][4] << endl;
    // testing 4 dimensions
    smart_array <bool, 4> r(2, 3, 4, 5);
    cout << "z.length(): " << r.length() << endl;
    cout << "z[0].length(): " << r[0].length() << endl;
    cout << "z[0][0].length(): " << r[0][0].length() << endl;
    cout << "z[0][0][0].length(): " << r[0][0][0].length() << endl;
    // testing copy constructor
    smart_array <float, 2> copied_y(y);
    cout << "copied_y.length(): " << copied_y.length() << endl;
    cout << "copied_y[0].length(): " << copied_y[0].length() << endl;

// new test
smart_array <bool, 4> r(2, "ops");
     cout << "r.length(): " << r.length() << endl;
     cout << "r[0].length(): " << r[0].length() << endl;
     cout << "r[0][0].length(): " << r[0][0].length() << endl;
     cout << "r[0][0][0].length(): " << r[0][0][0].length() << endl;


    cout << copied_y[0][0] << "\t" << copied_y[1][0] << "\t" <<
copied_y[0][1] << "\t" <<
        copied_y[1][1] << "\t" << copied_y[0][2] << "\t" << copied_y[1]
[2] << endl;
    return 0;
}

Output of program with the new test :
Hello world!

Hi Vladmir
static_assert is not a keyword in C++, it's going to be a keyword
though (C++0x). If you are using GCC you can pass -std=c++0x option to
the compiler, or you can comment it out for the time being.
 
J

Jonathan Lee

Basically I wrote it
for porting Java code to C++, where one can directly assign to some
arbitrary index without bothering about push_backs.

An interesting idea, but the usage pattern suggests a sparse array
type storage (to me). Like, you want default values everywhere except
where you specify. Presumably, you aren't specifying every element
or your class would simply _be_ vector and used like vector, i.e.,
you would know the size of each dimension and could construct the
vector directly.

You could probably get sparse store with a

map< vector<size_t>, T> sparse_array;

using the vector<size_t> to index the dimensions. Provide a
comparison function, wrap it with a class, and provide access
via operator()

smart_array<float, 3> sa;
sa(3, 5, 8) = 3.56;
sa(1, 6, 4) = 10.39;

Might be tricky getting the signature of operator() right.
Might want to use a fixed sized struct for a key instead, etc.
Anyway, just some thoughts.

--Jonathan
 
L

legends2k

legends2k wrote:

<snip>


class smart_array
{
    vector < smart_array<T, N - 1> > vec;
public:
    explicit smart_array(vector <size_t> &dimensions)
    {
        assert(N == dimensions.size());
        vector <size_t>::iterator it = ++dimensions.begin();
        vector <size_t> dimensions_remaining(it, dimensions.end());
        smart_array <T, N - 1> temp_smart_array(dimensions_remaining);
        vec.assign(dimensions[0], temp_smart_array);
    }
    explicit smart_array(size_t dimension_1 = 1, ...)
    {
        static_assert(N > 0, "Error: smart_array expects 1 or more
dimension(s)");

Error : static_assert not defined


        assert(dimension_1 > 1);
Sample Usage:
#include "smart_array.h"
#include <iostream>
using std::cout;
using std::endl;
int main()
{
    // testing 1 dimension
    smart_array <int, 1> x(3);
    x[0] = 0, x[1] = 1, x[2] = 2;
    cout << "x.length(): " << x.length() << endl;
    // testing 2 dimensions
    smart_array <float, 2> y(2, 3);
    y[0][0] = y[0][1] = y[0][2] = 0;
    y[1][0] = y[1][1] = y[1][2] = 1;
    cout << "y.length(): " << y.length() << endl;
    cout << "y[0].length(): " << y[0].length() << endl;
    // testing 3 dimensions
    smart_array <char, 3> z(2, 4, 5);
    cout << "z.length(): " << z.length() << endl;
    cout << "z[0].length(): " << z[0].length() << endl;
    cout << "z[0][0].length(): " << z[0][0].length() << endl;
    z[0][0][4] = 'c'; cout << z[0][0][4] << endl;
    // testing 4 dimensions
    smart_array <bool, 4> r(2, 3, 4, 5);
    cout << "z.length(): " << r.length() << endl;
    cout << "z[0].length(): " << r[0].length() << endl;
    cout << "z[0][0].length(): " << r[0][0].length() << endl;
    cout << "z[0][0][0].length(): " << r[0][0][0].length() << endl;
    // testing copy constructor
    smart_array <float, 2> copied_y(y);
    cout << "copied_y.length(): " << copied_y.length() << endl;
    cout << "copied_y[0].length(): " << copied_y[0].length() << endl;

// new test
smart_array <bool, 4> r(2, "ops");
     cout << "r.length(): " << r.length() << endl;
     cout << "r[0].length(): " << r[0].length() << endl;
     cout << "r[0][0].length(): " << r[0][0].length() << endl;
     cout << "r[0][0][0].length(): " << r[0][0][0].length() << endl;


    cout << copied_y[0][0] << "\t" << copied_y[1][0] << "\t" <<
copied_y[0][1] << "\t" <<
        copied_y[1][1] << "\t" << copied_y[0][2] << "\t" << copied_y[1]
[2] << endl;
    return 0;
}

Output of program with the new test :
Hello world!

@Vladimir: Yes, static_assert is part of C++0x, you can comment it if
you want pedantic C++ or pass -std=c++0x if you are using GCC. As for
the variadic argument ctor: yes, it has it's problems like in
smart_array <bool, 4> r(2, "ops"); the type and count of args passed
to the ctor can't be made strict.

@Jonathan: I get it Jonathan. But I am doing the whole thing in a very
restricted environment, BREW. It has a bare minimum C++ compiler with
no exceptions, no std (even std::vector is not there, I've written one
myself), etc. Hence I thought this would be an easier way around given
that I've a vector.
 
P

pfultz2

legends2k wrote:
class smart_array
{
    vector < smart_array<T, N - 1> > vec;
public:
    explicit smart_array(vector <size_t> &dimensions)
    {
        assert(N == dimensions.size());
        vector <size_t>::iterator it = ++dimensions.begin();
        vector <size_t> dimensions_remaining(it, dimensions.end());
        smart_array <T, N - 1> temp_smart_array(dimensions_remaining);
        vec.assign(dimensions[0], temp_smart_array);
    }
    explicit smart_array(size_t dimension_1 = 1, ...)
    {
        static_assert(N > 0, "Error: smart_array expects 1 or more
dimension(s)");
Error : static_assert not defined
        assert(dimension_1 > 1);
Sample Usage:
#include "smart_array.h"
#include <iostream>
using std::cout;
using std::endl;
int main()
{
    // testing 1 dimension
    smart_array <int, 1> x(3);
    x[0] = 0, x[1] = 1, x[2] = 2;
    cout << "x.length(): " << x.length() << endl;
    // testing 2 dimensions
    smart_array <float, 2> y(2, 3);
    y[0][0] = y[0][1] = y[0][2] = 0;
    y[1][0] = y[1][1] = y[1][2] = 1;
    cout << "y.length(): " << y.length() << endl;
    cout << "y[0].length(): " << y[0].length() << endl;
    // testing 3 dimensions
    smart_array <char, 3> z(2, 4, 5);
    cout << "z.length(): " << z.length() << endl;
    cout << "z[0].length(): " << z[0].length() << endl;
    cout << "z[0][0].length(): " << z[0][0].length() << endl;
    z[0][0][4] = 'c'; cout << z[0][0][4] << endl;
    // testing 4 dimensions
    smart_array <bool, 4> r(2, 3, 4, 5);
    cout << "z.length(): " << r.length() << endl;
    cout << "z[0].length(): " << r[0].length() << endl;
    cout << "z[0][0].length(): " << r[0][0].length() << endl;
    cout << "z[0][0][0].length(): " << r[0][0][0].length() << endl;
    // testing copy constructor
    smart_array <float, 2> copied_y(y);
    cout << "copied_y.length(): " << copied_y.length() << endl;
    cout << "copied_y[0].length(): " << copied_y[0].length() << endl;
// new test
smart_array <bool, 4> r(2, "ops");
     cout << "r.length(): " << r.length() << endl;
     cout << "r[0].length(): " << r[0].length() << endl;
     cout << "r[0][0].length(): " << r[0][0].length() << endl;
     cout << "r[0][0][0].length(): " << r[0][0][0].length() << endl;
    cout << copied_y[0][0] << "\t" << copied_y[1][0] << "\t" <<
copied_y[0][1] << "\t" <<
        copied_y[1][1] << "\t" << copied_y[0][2] << "\t" << copied_y[1]
[2] << endl;
    return 0;
}
Output of program with the new test :
Hello world!

@Vladimir: Yes, static_assert is part of C++0x, you can comment it if
you want pedantic C++ or pass -std=c++0x if you are using GCC. As for
the variadic argument ctor: yes, it has it's problems like in
smart_array <bool, 4> r(2, "ops"); the type and count of args passed
to the ctor can't be made strict.

@Jonathan: I get it Jonathan. But I am doing the whole thing in a very
restricted environment, BREW. It has a bare minimum C++ compiler with
no exceptions, no std (even std::vector is not there, I've written one
myself), etc. Hence I thought this would be an easier way around given
that I've a vector.

In C++ and Java arrays are passed as reference(or a pointer). Using a
vector does not mimic the behavior of java nor c++, because vectors
copy each element of the vector on copy, unlike arrays which only copy
the reference.
 
J

Juha Nieminen

legends2k said:
It's closer to an array in a way that all the members are already
initialized (by the default ctor); while when using std::vector one
has to call assign.

What makes you think so? If you specify the size of the vector in
its constructor, it will, naturally, initialize all the members.

Initializing a multi-dimensional vector in its constructor is slightly
verbose, but perfectly possible. For example a 2-dimensional vector is
typically initialized like this:

std::vector<std::vector<int> > vec2d(10, std::vector<int>(20, 0));

(Creates a 10x20 vector with all of its elements initialized to zero.)

Of course the more dimensions, the longer the initialization, but it's
just a bit of verbosity, not something insurmountable.
 
L

legends2k

  What makes you think so? If you specify the size of the vector in
its constructor, it will, naturally, initialize all the members.

  Initializing a multi-dimensional vector in its constructor is slightly
verbose, but perfectly possible. For example a 2-dimensional vector is
typically initialized like this:

  std::vector<std::vector<int> > vec2d(10, std::vector<int>(20, 0));

(Creates a 10x20 vector with all of its elements initialized to zero.)

  Of course the more dimensions, the longer the initialization, but it's
just a bit of verbosity, not something insurmountable.

@Juha: Agreed. Probably it's only a wrapper. But I lied the syntatic
sugar it provides, I feel it's elegant.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,149
Members
46,695
Latest member
StanleyDri

Latest Threads

Top