Corrade::Containers::ArrayTuple class new in Git master

Array tuple.

A set of arrays of heterogenous types and varying lengths stored in a single allocation. Compared to creating several Array instances, this has the advantage of using a single contiguous piece of memory with less allocator overhead and potentially better cache performance, especially if there's many short arrays. On the other hand, when there's many items or you need to individually grow each array, using dedicated Array instances may be a better option.

A common use case is when dealing with C APIs that accept pointers to several different arrays and the sizes are not known at compile time. The following snippet shows filling a Vulkan VkRenderpassCreateInfo structure with dynamic attachment, subpass and subpass dependency description. The data constructor will allocate a single block of memory for 3 attachments, 2 subpasses and 7 dependencies and points the attachments, subpasses and dependencies views to it:

Containers::ArrayView<VkAttachmentDescription> attachments;
Containers::ArrayView<VkSubpassDescription> subpasses;
Containers::ArrayView<VkSubpassDependency> dependencies;
Containers::ArrayTuple data{
    {3, attachments},
    {2, subpasses},
    {7, dependencies}
};

// Fill the attachment, subpass and dependency info...

VkRenderPassCreateInfo info{};
info.attachmentCount = 3;
info.pAttachments = attachments;
info.subpassCount = 2;
info.pSubpasses = subpasses;
info.dependencyCount = 7;
info.pDependencies = dependencies;

While concrete layout of the data array is internal to the implementation, the attachments, subpasses and dependencies views get set to correctly sized and aligned non-overlapping sub-ranges that you can fill afterwards. The memory is owned by the ArrayTuple instance, thus the views will be valid only for as long as the instance exists. It's up to you what happens to the views after — in the above case, all needed information is already contained in the info structure, so the views aren't needed after anymore.

Besides ArrayView, the output view can also be a (multi-dimensional) StridedArrayView, a MutableBitArrayView, a (multi-dimensional) MutableStridedBitArrayView or a MutableStringView. See constructor overloads of the Item class for reference.

Storing non-trivial types

The usage isn't limited to just trivial types — by default (or if you explicitly specify ValueInit) it'll value-construct the items and will also correctly call destructors at the end. Moreover, each sub-array is padded to match alignment requirements of its type. You can also specify NoInit, which will keep the contents uninitialized, allowing you to use a non-default constructor or skip zero-initialization of builtin types when not necessary:

Containers::ArrayView<std::string> strings;
Containers::ArrayView<Containers::Reference<std::string>> references;
Containers::ArrayTuple data{
    {ValueInit, 15, strings},
    {NoInit, 15, references}
};

/* Initialize all references to point to the strings */
for(std::size_t i = 0; i != strings.size(); ++i)
    new(references + i) Containers::Reference<std::string>{strings[i]};

Custom allocations and deleters

Like other containers, it's possible to provide custom-allocated memory. In this case it's slightly more complicated because the actual memory size isn't known beforehand, so you'll need to give the class an allocator and deleter function at once. The following rather contrived example shows creating a memory-mapped file for 400 MB of measurement data, which will allow it to be offloaded to disk in case of a memory pressure:

Containers::ArrayView<std::uint64_t> latencies;
Containers::ArrayView<float> averages;
Containers::ArrayTuple data{
    {{NoInit, 200*1024*1024, latencies},
     {NoInit, 200*1024*1024, averages}},
    [](std::size_t size, std::size_t)
        -> Containers::Pair<char*, Utility::Path::MapDeleter>
    {
        Containers::Optional<Containers::Array<char, Utility::Path::MapDeleter>>
            data = Utility::Path::mapWrite("storage.tmp", size);
        CORRADE_INTERNAL_ASSERT(data);
        Utility::Path::MapDeleter deleter = data->deleter();
        return {data->release(), deleter};
    }
};

See the ArrayTuple(const ArrayView<const Item>&, A) constructor documentation for a detailed description of the allocator and deleter signature.

Public types

class Item
Array tuple item.
using Deleter = void(*)(char*, std::size_t)
Deleter type.

Constructors, destructors, conversion operators

ArrayTuple(const ArrayView<const Item>& items = {}) explicit
Constructor.
ArrayTuple(std::initializer_list<Item> items) explicit
template<class A>
ArrayTuple(const ArrayView<const Item>& items, A allocator) explicit
Construct using a custom allocation.
template<class A>
ArrayTuple(std::initializer_list<Item> items, A allocator) explicit
ArrayTuple(const ArrayTuple&) deleted
Copying is not allowed.
ArrayTuple(ArrayTuple&& other) noexcept
Move constructor.
~ArrayTuple()
Destructor.
operator Array<char>() &&
Move-conversion to an Array.

Public functions

auto operator=(const ArrayTuple&) -> ArrayTuple& deleted
Copying is not allowed.
auto operator=(ArrayTuple&& other) -> ArrayTuple& noexcept
Move assignment.
auto data() -> char*
Array tuple data.
auto data() const -> const char*
auto size() const -> std::size_t
Array tuple data size.
auto deleter() const -> Deleter
Array tuple deleter.
auto release() -> char*
Release data storage.

Typedef documentation

typedef void(*Corrade::Containers::ArrayTuple::Deleter)(char*, std::size_t)

Deleter type.

Type returned by deleter(), see the function documentation for more information.

Function documentation

Corrade::Containers::ArrayTuple::ArrayTuple(const ArrayView<const Item>& items = {}) explicit

Constructor.

If the items view is empty, the constructed instance is equivalent to a moved-from state.

Corrade::Containers::ArrayTuple::ArrayTuple(std::initializer_list<Item> items) explicit

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

template<class A>
Corrade::Containers::ArrayTuple::ArrayTuple(const ArrayView<const Item>& items, A allocator) explicit

Construct using a custom allocation.

The allocator needs to callable or implement a call operator with a signature of Containers::Pair<char*, D>(*)(std::size_t, std::size_t). It gets passed a pair of desired allocation size and alignment of the allocation and should return a pair of an allocated memory pointer and a deleter instance that will be later used to delete the allocation. The allocation alignment is useful mainly when allocating over-aligned types, such as SIMD vectors, as C++ is only guaranteed to align correctly only for the largest standard types.

The deleter type D needs to be one of the following and it gets passed the allocation pointer together with its size (which was earlier passed to the allocator):

  • a (stateful) functor, implementing void operator()(char*, std::size_t),
  • a plain stateless function pointer void(*)(char*, std::size_t),
  • or a std::nullptr_t, which is equivalent to using the standard delete[].

In case of a stateful deleter, its contents are stored inside the allocated memory alongside other metadata. It gets copied to a temporary location before being called (to prevent it from freeing the memory from under itself) and its destructor is called afterwards.

template<class A>
Corrade::Containers::ArrayTuple::ArrayTuple(std::initializer_list<Item> items, A allocator) explicit

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

Corrade::Containers::ArrayTuple::ArrayTuple(ArrayTuple&& other) noexcept

Move constructor.

Resets data pointer and deleter of other to be equivalent to a default-constructed instance.

Corrade::Containers::ArrayTuple::~ArrayTuple()

Destructor.

Calls deleter() on the owned data().

Corrade::Containers::ArrayTuple::operator Array<char>() &&

Move-conversion to an Array.

Meant for dealing with APIs that accept untyped arrays as a storage. To avoid unforeseeable consequences stemming from the deleter storing its state inside the array it's deleting, the conversion is allowed only in case the array tuple stores trivially-destructible types and has either a default or a stateless deleter(). If you need to create an Array regardless, do it manually using deleter(), size() and release().

ArrayTuple& Corrade::Containers::ArrayTuple::operator=(ArrayTuple&& other) noexcept

Move assignment.

Swaps data pointer and deleter of the two instances.

char* Corrade::Containers::ArrayTuple::data()

Array tuple data.

Note that the data contents and layout is implementation-defined.

const char* Corrade::Containers::ArrayTuple::data() const

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

Deleter Corrade::Containers::ArrayTuple::deleter() const

Array tuple deleter.

If set to nullptr, the contents are deleted using standard operator delete[]. The returned type is always a plain function pointer even in case the array has a custom stateful deleter, as the function has to perform destructor calls for non-trivially-destructible array items before to executing the actual memory deleter.

char* Corrade::Containers::ArrayTuple::release()

Release data storage.

Returns the data pointer and resets data pointer, size and deleter to be equivalent to a default-constructed instance. Deleting the returned array is user responsibility — usually the array has a custom deleter() that additionally takes care of calling destructors of non-trivially-destructible array types. Using a plain delete[] is appropriate only if deleter() is nullptr.