FloatBuffer and BufferView Containers
wtf::FloatBuffer and wtf::BufferView both type-erase a buffer of
floating-point values. In practice, array-of-structure objects like
std::vector<Float> are performance inefficient and the point of
wtf::FloatBuffer and wtf::BufferView is to provide an abstraction over
the underlying memory so that user code looks/behave like it uses
std::vector<Float> as much as possible. The two classes exist to serve two
different relationships to
the underlying memory (see What is the Scope of WTF? for the original design goals):
wtf::FloatBufferowns the memory it type-erases.wtf::BufferViewaliases memory owned by someone else (aFloatBuffer, astd::vector, a raw pointer, …).
Both are implemented with the same “Interface”/”Holder”/”Model” type-erasure pattern described in Type-Erasure in WTF.
Design Considerations
FloatBuffer is vector-like, not array-like
Early on, FloatBuffer was a fixed-size once constructed – more like
std::array<T, N> than std::vector<T>. This led to a pattern where the
user had to: 1. Create a std::vector<Float> and then 2. Move the
std::vector<Float> into the FloatBuffer. This felt like a workaround
to get around the fact that FloatBuffer was not vector-like. So we opted
to make FloatBuffer vector-like, so that it can be constructed empty and
then grow as needed.
BufferView is span-like, not vector-like
BufferView intentionally does not get push_back/reserve.
A view aliases memory it does not own; there is nothing for it to grow into
without invalidating whatever it is aliasing (and, if it aliased a
std::vector that reallocated, the view would be left dangling regardless).
So BufferView stays span-like: a fixed-size, non-owning window onto
another object’s contiguous memory.
Misc. Notes
Implementing push_back for a type-erased T
We want push_back to be able to accept raw floating-point values (e.g.
double), wtf::Float, or wtf::FloatView. In each case the
implementation should work out what concrete C++ type to store. For the raw-
value overload this is trivial – the type is a template parameter, known at
compile time. For Float/ FloatView it is only known at runtime.
The easy type-erasure scenario is when *this already holds a buffer: the
held buffer’s element type is known (via RTTI), so the incoming Float/
FloatView just needs to be un-erased to that type (via fp::float_cast,
throwing std::runtime_error on a mismatch, the same throw-on-mismatch idiom
used everywhere else in WTF) and appended.
The hard case is appending a Float/ FloatView to an empty
FloatBuffer – because we need to work out what type the buffer should hold.
The first solution we tried relies on the fact that the Float/
FloatView’s own internal model knows the held type, so if it can build the
ContiguousModel directly we would not need a candidate list. The problem is
that we have a firm buffer depends on fp architecture, i.e., the
fp module needs to be fully resolved before the buffer module can be
compiled. Having Float/ FloatView’s model build the ContiguousModel
directly would require us to resolve part of the buffer module before the
fp module can be compiled, which is a circular dependency.
The current solution is to have the caller supply a TupleType of candidate
types, exactly the same mechanism Performing Type-Safe Operations describes for dispatching
operations.