The Developer’s Cry

Yet another blog by a hobbyist programmer

Putting the fun back in C with array slicing and dicing (revisited)

I’ve been writing a lot about putting the fun back in C by leveraging C++. Using strings, arrays, and dictionaries you can really speed up development time, as it makes C act as a scripting language, while maintaining a high level of performance.
Last July I wrote about array slices, and implemented a copy-on-write array that uses a shared_ptr to point at the actual data. Because shared_ptr, the array can be sliced and sliced again, and they all share the same backing store. This is great for getting scripting language-like behavior, but now I want to get back to game programming. Game code asks for a different style of programming—more crude, closer to the metal. Therefore we will revisit arrays and slices, and implement a new slice class.

To be truthful, for the implementation of such a slice class I had a good look at how slices work in the Go language. In Go, an array is a very static and bare-bones entity: it’s just a sequence of bytes. The data structure to actually work with, is the slice. A slice has a pointer to the array, and it knows the size of the array (capacity). On top of that, it has a length so you can use the array slice as a (string) buffer, stack, etcetera. An image tells a thousand words:

If you want to create a new slice plus backing array, it has to be allocated with make(), like so:

// this is Go code
s := make([]byte, 256)

Note however that the size is fixed, and the array does not grow beyond its limits. For a language like Go I find it quirky and primitive, but this is exactly what I need for my game code. And that code is in C, so here we go.

template <typename T>
struct slice {
    uint len, cap;
    T *data;
    bool owner;

    ...

    T& operator[](int idx);
    void append(const T& elem);
    T& pop(int idx=-1);
    slice<T> operator()(int start, int end) const;
    void clear(void);
};

It’s ironic how there is nothing in Go that you can not do in C++.
Caveat: We stuck a raw pointer in there. Ideally, it should be a shared_ptr. Unfortunately it’s a rats nest because C++ shared_ptr doesn’t properly handle shared arrays. Grunt, recall that I just said that there is nothing you can not do in … (!)
A clean solution would either be based on std::vector, or be just like the array class that we already wrote.
My actual code doesn’t care so much and deals with it more akin to unique_ptr; it tracks the ownership of the backing array via a bool. It is left up to the programmer herself to make sure that once the “owning” slice has been destroyed, any subslices can no longer be used. It’s not perfect, but this compromise works out pretty well in practice.

We can easily mimic the len() and cap() functions:

template <typename T>
uint len(const slice<T>& s) {
    return s.len;
}

template <typename T>
uint cap(const slice<T>& s) {
    return s.cap;
}

Note how len() and cap() could also be implemented as friend functions. For now, I’m writing sloppy C++ code and I don’t care about encapsulation one bit. More on that later.

And we can mimic Golang’s make() function:

template <typename T>
slice<T> make(uint n) {
    assert(n != 0);
    return slice<T>(new T[n], 0, n, true);
}

Finally, there is a copy() function. I don’t have much use for it, really. I think that’s mostly because game code doesn’t need slice or array copies.

template <typename T>
void copy(slice<T>& dest, const slice<T>& src) {
    // basically this, but ... read below
    for(uint i = 0; i < n; i++) {
        dest[i] = src[i];
    }
}

In this case, using operator=() is important (opposed to memcpy()) because we may be dealing with C++ objects rather than just raw bytes. (You may use template specialization for an optimized case when dealing with POD types).
Also mind that slices may overlap. If the destination overlaps with the source, then you should do a backwards copy because it will overwrite elements in the backing array.

On the surface, the implementation of our slice class is similar to the array class that we previously wrote. The static nature of the backing array changes the game though, and it handles very differently in practice.

Game code asks for a rudimentary approach. This style of code takes some shortcuts so it doesn’t like to bother with shared_ptr (although you can, as noted), OOP intrinsics such as public/private membership, and it uses assert() rather than “always-on” runtime bounds checking. It simply bails out when something is amiss.
Whatever the code style, you are always going to need data structures and a pleasant API; the programmer will always want to have good tooling at hand to get the job done. C++ is a terribly over-complicated language, but it’s a good fit for API-building and putting the fun back in C.