The core mechanism of AnyContainer
is having the policy tell it the MemoryLayout
of the local storage to do everything. The MemoryLayout
also provides the "micro API". Without policy compositions the affordances (including user affordances) can change the MemoryLayout
via the CRTP.
However, this is not possible with composed policies, below is the explanation.
Another key is that the actual objects are managed by different implementations based on the properties of the object to be handled: For example, in the current state of the framework, small enough, with compatible alignment and noexcept
move constructor, they will reside locally, otherwise, through the heap; there are these two families of implementations, the local buffer and the heap. There are more ways, for example memory arenas for instances of the same type, so the possibilities for implementations on how to manage the objects are not bounded.
At the initial design of AnyContainer
we chose that the implementations would be substitutable by the MemoryLayout
, that is, we committed to an inheritance relationship.
To achieve substitutability the language puts us in a corner that basically only leaves the tool of converting pointer-to-base-class into pointer-to-derived-class this is the ultimate direction in which the language is evolving to; and this is the hardest complication of all of the framework:
The implementations must inherit from MemoryLayout
.
Therefore, if we want to substitute among AnyContainer<P1>
and AnyContainer<P2>
, we can only do it if P1::MemoryLayout
is a base class of P2::MemoryLayout
, and at the same time, to be valid, the implementations in P2 must inherit from the implementations in P1, taken together these two things force that P1::MemoryLayout
is the same as P2::MemoryLayout
, and then the extra affordances in P2
are not allowed to change the MemoryLayout
via the CRTP. In essence, the only way to achieve policy composition is when the MemoryLayout
is polymorphic without changing its C++ type. In the case of vtable-based policies, this is achieved by extending the vtables the MemoryLayout
points to with the extra affordances, again, using an inheritance relationship.
However, the pre-existing way for AnyContainer
to reach into MemoryLayout
for the things the affordances need does not work for the extra affordances in P2
. All AnyContainer
"knows" is its MemoryLayout
and P2
.
ExtendedBasePolicy
is a policy composed from BasePolicy
and its own extra affordances, that is, the result of
DerivedPolicy<BasePolicy, X, Y, Z>
where X
, Y
, Z
are affordance specifications. Notice that BasePolicy
is not a base class of ExtendedBasePolicy
.
We want the following code to be completely valid:
AnyContainer<ExtendedBasePolicy> ebp;
AnyContainer<BasePolicy> *bpp = &ebp, &bpr = ebp;
The language forces us to make AnyContainer<ExtendedBasePolicy>
a derived class of AnyContainer<BasePolicy>
.
Above we have explained the VTables are compatible.
There is the concept of type switch lurking again: the vtable is the type switch in the VTable based policies. The natural way to implement the vtables is to emulate "virtual base classes", since we want the behaviors of the most derived class to influence the resulting object, progressively to the most basic. However, we want the opposite for the value being held: to build it from the most basic to the most derived, in that way, each layer can naturally add their "construction" bit to the object, but alas, this can not happen because the value managers need to be built from the most derived.
Thus, all of the containers in the middle need "bypassing", this is accomplished by using a type token