Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ArrayRef

ArrayRef<T> references an array of a dynamic size. It is a simple non-owning pointer that contains a base pointer of type T * and a size. ArrayRef is analogous to C++20 std::span, or a Rust slice.

ArrayRef doesn’t do much by itself, and is mostly used on API boundaries to make it ergonomic to pass “an array” as an argument in various ways:

#include <peel/ArrayRef.h>

using namespace peel;

void takes_int_array (ArrayRef<int> arr);

/* a C array (size deduced) */
int my_array[5] = { 1, 2, 3, 4, 5 };
takes_int_array (my_array);

/* base pointer + count */
int *my_ptr = /* ... */;
size_t my_count = /* ... */;
takes_int_array ({ my_ptr, my_count });

/* nullptr */
takes_int_array (nullptr);

/* ArrayRef instance */
ArrayRef<int> another_array = /* ... */;
takes_int_array (another_array);

To take apart an ArrayRef, getting a base pointer and a size, you can use the data and the size methods respectively. This is also compatible with C++17 std::data and std::size.

ArrayRef supports common C++ idioms, for example it can be indexed and iterated over. This also means large parts of the C++ algorithm library should work with it.

ArrayRef<Gtk::Widget *> widgets = /* ... */;

Gtk::Widget *first_widget = widgets[0];

for (Gtk::Widget *widget : widgets)
  g_print ("%s\n", widget->get_name ());

ArrayRef can also be sliced, which produces a new ArrayRef referring to a part (slice) of the original array. This is useful when working with data, for instance here’s what the body of a read-write echo loop might look like:

Gio::InputStream *input_stream = /* ... */;
Gio::OutputStream *output_stream = /* ... */;

/* read some data */

unsigned char buffer[1024];
auto n_read = input_stream->read (buffer, nullptr, nullptr);
/* deal with errors... */

/* write back what we have read */

ArrayRef<unsigned char> to_write { buffer, n_read };
while (to_write)
  {
    auto n_written = output_stream->write (to_write, nullptr, nullptr);
    /* deal with errors... */
    to_write = to_write.slice (n_written, to_write.size () - n_written);
  }

Mutability

ArrayRef<T> is a mutable version, in that it’s possible to mutate the values of type T that it references through the ArrayRef. ArrayRef<const T> is the constant version which does not allow mutation. This is very similar to the difference between T * and const T *.

The following two functions from libdex demonstrate the difference:

RefPtr<Dex::Future>
Dex::input_stream_read (Gio::InputStream *self, ArrayRef<uint8_t> buffer, int io_priority) noexcept;

RefPtr<Dex::Future>
Dex::output_stream_write (Gio::OutputStream *self, ArrayRef<const uint8_t> buffer, int io_priority) noexcept;

Dex::input_stream_read’s buffer argument is a mutable array of bytes that the call will fill with read data, whereas Dex::output_stream_write’s buffer argument is a read-only array of bytes.

See examples/libdex.cpp in the peel repository for an example of using ArrayRef and libdex APIs.

Fixed size arrays

ArrayRef is used for arrays whose size is only known dynamically. For fixed-size arrays, peel instead uses C++ references to C arrays1. For example, Gsk::BorderNode::create accepts two arrays, both of them having fixed size of four, and the Gsk::BorderNode::get_widths getter returns the widths:

RefPtr<Gsk::BorderNode>
Gsk::BorderNode::create (const Gsk::RoundedRect *outline, const float (&border_width)[4], const Gdk::RGBA (&border_color)[4]) noexcept;

const float
(&Gsk::BorderNode::get_widths () const noexcept)[4];

While the declaration syntax may look somewhat intimidating2, this enables you to pass a C array of a matching size in a very natural way:

Gsk::RoundedRect outline = /* ... */;
float border_width[4] = /* ... */;
Gdk::RGBA border_color[4] = /* ... */;

auto node = Gsk::BorderNode::create (&outline, border_width, border_color);

On the other hand, attempting to pass an array of a wrong size will result in a type error. (You can use an explicit cast to silence the error if you think you know what you’re doing.)

Using a returned fixed-size array is similarly straightforward:

Gsk::BorderNode *node = /* ... */;
auto &widths = node->get_widths ();

g_print ("%f\n", widths[3]);

Note the & on widths declaration; if you omit it, a local array will be declared instead of a reference to one, and the returned array will be immediately copied out into the local array3.

Thanks to the type being a reference to an array and not a plain pointer, the compiler should be able to emit a warning if you use an out-of-bounds index:

warning: array index 42 is past the end of the array (that has type 'const float[4]') [-Warray-bounds]

  1. this is a somewhat unusual combination, leading to a farily weird syntax, and also about the only place where C++ references show up in peel APIs (other than in special members like copy constructors)

  2. to humans and machines alike: the peel bindings generator required significant enhancements to be able to produce this syntax

  3. considering that fixed-size arrays typically have a fairly small size (in this case, 4), this may be just fine too