4

Here is a code from b2Math.h from Box2d physics engine.

struct b2Vec2
{   ...
    /// Read from and indexed element.
    float operator () (int i) const
    {
         return (&x)[i];
    }
    /// Write to an indexed element.
    float operator () (int i)
    {
         return (&x)[i];
    }
    ...
    float x, y;
}

Why can't we just use SomeVector.x and SomeVector.y to read/write vector coordinates? And how actually line return (&x)[i]; works? I mean, the array brakets [] after the reference to x component of the struct isn't clear for me.

Thanks in advance for your reply.

Louis Langholtz
  • 2,913
  • 3
  • 17
  • 40
  • "Why can't we just use SomeVector.x and SomeVector.y to read/write vector coordinates?" How do you declare `SomeVector`? This is not in your code sample here. – Code-Apprentice Apr 13 '17 at 17:25
  • 6
    `(&x)[i]` exhibits undefined behavior for any `i` other than `0`, as it will attempt to perform array indexing at memory which is not allocated for such an array. – Cory Kramer Apr 13 '17 at 17:25
  • 1
    There is an error in the modifying `operator()`: should return `float&`. – iksemyonov Apr 13 '17 at 17:26
  • 1
    It looks like you're missing important information to fully answer the question. However, "Why can't we just use SomeVector.x and SomeVector.y to read/write vector coordinates?" is easy, you can :) – George Apr 13 '17 at 17:26
  • @CoryKramer I'm pretty sure in most implementations `x` and `y` are adjacent in memory. Is this not guaranteed? (Note I am not the OP.) – Code-Apprentice Apr 13 '17 at 17:27
  • @iksemyonov Why should `operator()` return a reference? – Code-Apprentice Apr 13 '17 at 17:28
  • @Code-Apprentice No that is not guaranteed (there could be padding) and even if they are next to each other you are still accessing memory the pointer does not own which is UB. – NathanOliver Apr 13 '17 at 17:28
  • @Code-Apprentice [It depends if the class/struct is POD](https://stackoverflow.com/questions/422830/structure-of-a-c-object-in-memory-vs-a-struct), that may be the case here but it is generally dangerous, if not unreadable to assume so and try to play pointer arithmetic games like this anyway. – Cory Kramer Apr 13 '17 at 17:28
  • @Code-Apprentice it's said to be modifying, how does it modify given the code? The only way for it to modify `x` or `y` is by returning a reference to them and then assigning a new value to it: `SomeVector(0) = 5;`. – iksemyonov Apr 13 '17 at 17:31
  • @Code-Apprentice Default constructo does nothing, b2Vec2(){} Also here is the Set function: void Set(float mX, float mY){ x = mX; y = mY; } – Pathbreaker Apr 13 '17 at 17:44
  • @user402700 That still doesn't show a declaration for `SomeVector` as I asked in my first comment. I assume you mean you have a variable `b2Vec2 SomeVector`. Which as someone already said, you can use exactly like you said: `SomeVector.x` and `SomeVector.y`. – Code-Apprentice Apr 13 '17 at 17:47
  • The SomeVector defined as `b2Vec2 SomeVector(a, b);` Here is the constructor using coordinates: `b2Vec2(float xIn, float yIn) : x(xIn), y(yIn) {}` I know that it's possible to change the vector `x` and `y` directly with the Set method. But I want to know for what purpose the operator() was overloaded and how it works. For example, with this method we can get the `SomeVector.y` by typing `SomeVector(1)`. How this code gets the y from the reference to x `(&x)`? – Pathbreaker Apr 13 '17 at 18:20
  • @molbdnilo as author of the library said, library code uses different tricks for better performance. I think here is one of them. – Pathbreaker Apr 13 '17 at 18:26

1 Answers1

0

Here is a code from b2Math.h from Box2d physics engine.

There appears to be an error in the copy and paste of the Box2D source code that you've posted. Particularly, there appears to be a missing ampersand in the non-const method.

Also, this code snippet appears to from a codebase other than the current 2.3.2 released code.

Here's the portion from the Box2D 2.3.2 sources on GitHub:

/// Read from and indexed element.
float32 operator () (int32 i) const
{
    return (&x)[i];
}

/// Write to an indexed element.
float32& operator () (int32 i)
{
    return (&x)[i];
}

Why can't we just use SomeVector.x and SomeVector.y to read/write vector coordinates?

We can, and we usually do.

There is however some code in Box2D (b2AABB::RayCast specifically) that appears to have been written from an algorithm (the comment for b2AABB::RayCast saying "From Real-time Collision Detection, p179") that iterates over x and y as array subscripts zero and one. I'd guess that Erin Cato (the author of Box2D) implemented these operators this way: (a) to make access stylistically conform more to the algorithm, (b) to make it work, and (c) to make it work in a way that appears efficient. I can confirm that it does at least work.

I have my own fork of Box2D in which I've rewritten these operators. I changed its interface to using [] (instead of ()), and its implementation to using a switch statement to explicitly access either x or y. The latter I did to clearly-to-me avoid potentially undefined behavior w.r.t. the C++ standard.

Here's a snippet of the relevant portions of what my implementation looks like instead (and please note that I do not claim this to be perfect or good even, only that its an alternative implementation that works and that it should clearly be relying on defined behavior):

/// Accesses element by index.
/// @param i Index (0 for x, 1 for y).
auto operator[] (size_type i) const
{
    assert(i < max_size());
    switch (i)
    {
        case 0: return x;
        case 1: return y;
        default: break;
    }
    return x;
}

/// Accesses element by index.
/// @param i Index (0 for x, 1 for y).
auto& operator[] (size_type i)
{
    assert(i < max_size());
    switch (i)
    {
        case 0: return x;
        case 1: return y;
        default: break;
    }
    return x;
}

And how actually line return (&x)[i]; works?

As I implied above, I have looked into this exact question before.

It works when the memory layout of the x and y member variables of the structure is the same as having an array of two floats; which it typically does. So the ampersand (&) takes the address of the x parameter and then treats that address like an address to the start of an array which it then indexes by i.

I don't think this is defined behavior however as far as the C++ standard is concerned though. It wasn't clearly defined enough for my taste however.

Hope this answers your questions.

Louis Langholtz
  • 2,913
  • 3
  • 17
  • 40