Expected/Desired Behavior or Experience:
Closer compile-time compatibility with C++17-styled std::array
elements. Generic data access via templates to the underlying elements.
Actual Behavior:
Most places in the current code base use .x
and .y
values as expected as horizontal and vertical values. Some places however (like in the Joint
implementation code) overload these semantic notions (of the horizontal and the vertical) with other notions. Like treating them as array elements and offering indexed accessors (see float32& b2Vec2::operator()(int32 i);
for example). This overloading has been noted before by users of Erin's Box2D.
Discussion
When I forked Box2D from Erin's codebase which had float32& b2Vec2::operator()(int32 i);
in it, I changed this method to instead overload the indexing operator for this functionality and to use a switch statement to access .x
and .y
components. This served the purpose of using the []
operator which seems more conventional to me for indexed access to data, and served the purpose of avoiding relying the memory layout being compatible. For the sake of discussion, I'll call the former concept a structure of fields and the latter concept an array of elements.
Since then, I've become less fond of the conceptual bias of these structures being collections of fields and more fond of the bias towards being arrays of elements.
For one thing, while .x
and .y
are just names and access is 1 less character to type than say [0]
and [1]
, I like ascribing meaning to names and enforcing those when possible.
PlayRho could have the existing Vec2
named type being a type alias to Vector2D<Real>
like it does now with x
and y
fields and drop the indexed access support, and gain a separate type like Vec2A
which would have indexed access support but not the x
and y
fields. Alternatively, PlayRho could just drop the .x
and .y
field access support, and just use an array of elements.
With the former stance, the meanings I see are:
- Components are instance of their type.
- Components are intended to be contiguous in memory.2.
- Components are horizontal and vertical values.
With the latter stance, the meanings I see are:
- Components are instances of their type (still).
- Components are intended to be contiguous in memory.
So the latter stance loses a meaning. Sort of. The former stance is kind of like a union though in not being so C++ friendly as say C++17 std::variant
, because the former stance doesn't ensure that a Vec2
set as an x
and y
aggregate isn't later accessed as an array nor vice-versa. In other words, in the current implementation, it doesn't enforce three intentions; just two of them. So I ask, why not drop the charade and just go with the latter stance that doesn't impede components [0]
and [1]
from being used as x and y concepts nor from being used as a pair of impulse concepts either (as is done in the solver code).
For a second and perhaps more important thing however, while .x
and .y
access is 1 less character to type than say [0]
and [1]
, it seems to me that pure array styled access lends itself more directly to generic templated code. Instead of having code in Vector2D
and Vector3D
, we could have a base Vector
template structure that Vector2D
and Vector3D
were simply alias templates for. Some code for Vector
could then be shared for Vector2D
and Vector3D
like support for begin
and end
iterator methods.
This second attribute also seems to have beneficial implications for Mat22
and Mat33
structures which currently aren't templated. But I'd like them to be templates so that we can say have a Matrix22<Mass>
for instance which is actually how many joints use Mat22
now but with the types stripped down to base Real
values.
This second attribute also seems to have beneficial implications for things like a more natural application of std::inner_product
and other standard library functionality.