Lumiera  0.pre.03
»edit your freedom«
several-builder.hpp File Reference

Go to the source code of this file.

Description

Builder to create and populate instances of the lib::Several container.

For mere usage, inclusion of several.hpp should be sufficient, since the container front-end is generic and intends to hide most details of allocation and element placement. It is an array-like container, but may hold subclass elements, while exposing only a reference to the interface type.

Implementation data layout

The front-end container lib::Several<I> is actually just a smart-ptr referring to the actual data storage, which resides within an array bucket. Typically the latter is placed into memory managed by a custom allocator, most notably lib::AllocationCluster. However, by default, the ArrayBucket will be placed into heap memory. All further meta information is also maintained alongside this data allocation, including a deleter function to invoke all element destructors and de-allocate the bucket itself. Neither the type of the actual elements, nor the type of the allocator is revealed.

Since the actual data elements can (optionally) be of a different type than the exposed interface type I, additional storage and spacing is required in the element array. The field ArrayBucket<I>::spread defines this spacing and thus the offset used for subscript access. The actual data storage starts immediately behind the ArrayBucket, which thus acts as a metadata header. This arrangement requires a sufficiently sized raw memory allocation to place the ArrayBucket and the actual data into. Moreover, the allocation code in ElementFactory::create() is responsible to ensure proper alignment of the data storage, especially when the payload data type has alignment requirements beyond alignof(void*), which is typically used by the standard heap allocator; additional headroom is added proactively in this case, to be able to shift the storage buffer ahead to the next alignment boundary.

Handling of data elements

The ability to emplace a mixture of data types into the storage exposed through the lib::Several front-end creates some complexities related to element handling. The implementation uses generic rules and criteria based approach to decide on a case by case base if some given data content is still acceptable. This allows for rather tricky low-level usages, but has the downside to detect errors only at runtime — which in this case is ameliorated by the limitation that elements must be provided completely up-front, through the SeveralBuilder.

  • in order to handle any data element, we must be able to invoke its destructor
  • an arbitrary mixture of types can thus only be accepted if we can either rely on a common virtual base class destructor, or if all data elements are trivially destructible; these properties can be detected at compile time with the help of the C++ <type_traits> library
  • this container can accommodate non-copyable data types, under the proviso that the all the necessary storage is pre-allocated (using reserve() from the builder API)
  • otherwise, data can be filled in dynamically, expanding the storage as needed, given that all existing elements can be safely re-located by move or copy constructor into a new, larger storage buffer.
  • alternatively, when data elements are even ''trivially copyable'' (e.g. POD data), then it is even possible to increase the placement spread in the storage at the point when the requirement to do so is discovered dynamically; objects can be shifted to other locations by std::memmove() in this case.
  • notably, lib::AllocationCluster has the ability to dynamically adapt an allocation, but only if this happens to be currently the last allocation handed out; it can thus be arranged even for an unknown number of non-copyable objects to be emplaced when creating the suitable operational conditions. A key point to note is the fact that the container does not capture and store the actual data types persistently. Thus, the above rules must be applied in a way to always ensure safe handling of the contained data. Typically, the first element actually added will »prime« the container for a certain usage style, and after that, some other usage patterns may be rejected.

Todo:
this is a first implementation solution from 6/2025 — and was deemed roughly adequate at that time, yet should be revalidated once more observations pertaining real-world usage are available...
Warning
there is a known problem with over-alligned-types, which becomes relevant when the interface type has only lower alignment requirement, but an individual element is added with higher alignment requirements. In this case, while the spread is increased, still the placement of the element-type E is used as anchor, possibly leading to misalignment.
See also
several-builder-test.cpp

Definition in file several-builder.hpp.

#include "lib/error.hpp"
#include "lib/several.hpp"
#include "include/limits.hpp"
#include "lib/iter-explorer.hpp"
#include "lib/format-string.hpp"
#include "lib/util.hpp"
#include <type_traits>
#include <functional>
#include <cstring>
#include <utility>
#include <vector>

Classes

struct  AllocationPolicy< I, E, ALO >
 Policy Mix-In used to adapt to the ElementFactory and Allocator. More...
 
class  ElementFactory< I, ALO >
 Generic factory to manage objects within an ArrayBucket storage, delegating to a custom allocator ALO for memory handling. More...
 
struct  SetupSeveral< ALO, ALO< X > >::Policy< I, E >
 
struct  SetupSeveral< ALO, ARGS >
 Extension point: how to configure the SeveralBuilder to use an allocator ALO, initialised by ARGS. More...
 
struct  SetupSeveral< ALO >
 Specialisation: use a monostate allocator type ALO. More...
 
struct  SetupSeveral< ALO, ALO< X > >
 Specialisation: store a C++ standard allocator instance, which can be used to allocate objects of type X. More...
 
class  SeveralBuilder< I, E, POL >
 Builder to create and populate a lib::Several<I>. More...
 

Typedefs

template<class I , class E >
using HeapOwn = AllocationPolicy< I, E, std::allocator >
 Default configuration to use heap memory for lib::Several.
 

Functions

size_t constexpr alignRes (size_t alignment)
 determine size of a reserve buffer to place with proper alignment
 
template<typename I , typename E = I>
SeveralBuilder< I, E > makeSeveral ()
 Entrance Point: start building a lib::Several instance. More...
 
template<typename X >
SeveralBuilder< X > makeSeveral (std::initializer_list< X > ili)
 
template<typename TY >
size_t constexpr reqSiz ()
 Helper to determine the »spread« required to hold elements of type TY in memory with proper alignment.
 

Variables

const uint INITIAL_ELM_CNT = 10
 number of storage slots to open initially; starting with an over-allocation similar to std::vector
 

Namespaces

 lib
 Implementation namespace for support and library code.