Number47
Number47

Reputation: 493

C++ array wrapper

I've wanted a wrapper around arrays, such, it would be stored at the stack - to be not concerned about memory releasing - be initializable via brace lists, and possibly be substitutable in any place of an ordinary array. Then, I've produced the following code. And now am wondering, have I missed something. -- So - is it what I've wanted?

template<class T, size_t size>
struct Array
{
    T body[size];

    operator T* () { return body; }
};

Edit:

I might be imprecise. The wrapper is only for constructional purpose. It shall be used for constructing arrays from brace lists, when being in an initialization list (primarily). Like

class A {
    protected: A(int array[])
    ...

class B : public A {
    public: B() : 
        A( (Array<int, 2>) {{ 1, 2 }} )
        ...

There was a proposition of a const version of the casting operator. - I've been considering this, but am not sure, is it really needed. While casting to const T[] is done implicitly through the existing operator, and a constant array can be defined by giving T = const ..., is there still a reason?

Upvotes: 1

Views: 4707

Answers (1)

David G
David G

Reputation: 96810

For a basic example, I don't think there's much you can improve on, except for a few helper functions. In particular, it would be nice to have a method that returns the size:

constexpr std::size_t size() const { return size; }

In addition, here are a few others:

  • const/non-const overloads of operator[N]:

    As @ChristianRau stated in the comments, a operator T* provides a non-const version. We can implement the const version as such:

    T const& operator [](std::size_t n) const
    {
        return body[n];
    }
    // similarly for non-const:
    T& operator [](std::size_t n) { return body[n]; }
    
  • begin() and end() sequencers (very useful e.g. for the C++11 range-based for):

    T* begin() { return body; }
    T* end()   { return body + size; }
    
    // const versions... very much the same
    T const* cbegin() const { return body; }
    T const* cend()   const { return body + size; }
    T const* begin()  const { return cbegin(); }
    T const* end()    const { return cend(); }
    
  • an at() method, which includes bounds checking (as opposed to operator[] by convention):

    T const& at(std::size_t offset) const
    {
        // You should do bounds checking here
        return body[offset];
    }
    // also a non-const version again..
    
  • It would also be nice to have a constructor that takes an std::initializer_list<T> so that you don't have to use the aggregate-initialization:

    #include <algorithm>
    #include <initializer_list>
    
    template <typename T, std::size_t N>
    struct Array
    {
        ...
        Array(std::initializer_list<T> const& list)
        {
            std::copy(list.begin(), list.end(), body);
        }
        ...
    };
    

    Here is another one suggested by @DyP (initializer list always copies, perfect forwarding tries to avoid that):

    template <typename T, std::size_t N>
    struct Array
    {
        ...
        template <typename... Args>
        // possibly constexpr
        Array(Args&&... args) : body{ std::forward<Args>(args)... } 
        {}
        ...
    };
    

Here is the program in action if you want to see it -- http://ideone.com/Zs27it#view_edit_box

There are others features you can include, but as I said this is a basic example, and you would most likely be better off using something like std::array which has more or less the same methods.

Upvotes: 6

Related Questions