0

I have a vector 3D class

class Vector3D{
    public: float x; float y; float z;
    //some functions, e.g. operator+ - * / 
    //some 3D-specific function
};

and a vector N-D class.

template<int constSize> class VecFloatFix{
    float database[constSize];
    //some functions, e.g. operator+ - * / 
};

I noticed that there is code-duplication between two classes, so I think I should make Vector3D derived from VecFloatFix<3> :-

class Vector3D : public VecFloatFix<3>{
    //some 3D-specific function
};

Everything seems to be good, except that there are a lot of user code access Vector3D::x,y,z directly.

Is it possible to make Vector3D derived from VecFloatFix<3> while not break user's code?

My best guess is around :-

template<int constSize> class VecFloatFix{
    union{
        float database[constSize];
        float x,y,z;  ?????  sound like a hack
    }
    //some functions, e.g. operator+ - * / 
};

Edit: Hardcoding x,y,z into VecFloatFix is unsustainable.
If I have a new class Vector2D that derived from VecFloatFix<2>, Vector2D::z will compile fine (dangerous).

javaLover
  • 6,347
  • 2
  • 22
  • 67
  • 2
    If `database` in `VecFloatFix` is `protected`, or `public`, you could do something like `float& x = database[0];` in `Vector3D` class. – Algirdas Preidžius Mar 19 '17 at 10:03
  • 2
    There are various workarounds that might get you what you want, but it depends on what your client code is doing. Ultimately, I'd question the value of what you are trying to do. Just keep the two types separate and provide facilities for their interoperation (e.g. conversion between the two). – Joseph Thomson Mar 19 '17 at 10:03
  • @Algirdas Preidžius That is genius! I don't know it is possible. – javaLover Mar 19 '17 at 10:05
  • 1
    in your example x,y,z will be placed in one float (database[0]), so vec.x = 100.0 will make vec.y==100.0 too. You can make `struct {float x,y,z;};` to avoid this. – em2er Mar 19 '17 at 10:06
  • is it on purpose that `x`,`y`,`z` are public, while `database` is private? – 463035818_is_not_an_ai Mar 19 '17 at 10:07
  • @Joseph Thomson Thank. It is a good idea. My client read&write `x,y,z`, but never cache its address. – javaLover Mar 19 '17 at 10:07
  • @tobi303 Yes, it is by-design. `x y z` is public to shorten calling (math/physic notation), while `VecFloatFix` is kept as private for encapsulation (I also provide `operator[]` though). – javaLover Mar 19 '17 at 10:08
  • @em2er Thank, that is very useful. – javaLover Mar 19 '17 at 10:08
  • 1
    AAlgirdasPreidžius This will most likely result in a lot of wasted space per object. – n. m. could be an AI Mar 19 '17 at 10:24
  • @n.m. I hate when you are always correct. http://ideone.com/HhoDMo (sizeof = 12 vs 40) no.... you destroy my dream again – javaLover Mar 19 '17 at 10:27
  • 1
    References typically take space (this is permitted though not mandated by the standard). – n. m. could be an AI Mar 19 '17 at 10:29
  • 1
    Seems like we might learn a lesson here: If you make fields public, it is hard to change the implementation later without affecting user code. – Bo Persson Mar 19 '17 at 10:52
  • I don't know how many undefined behavior it will bring, but — is it possible to use `union Vector3D { VecFloatFix<3> _base; struct { float x, y, z; }; };`? – kennytm Mar 19 '17 at 10:55
  • @Bo Persson I am stubborn. I still think the design is correct. `solve(a.x,a.y,c.x,c.y)` is much cuter than `solve(a.x(),a.y(),c.x(),c.y())`. If I have no choice, I might use the notorious macro. *bwa bwaaa...* – javaLover Mar 19 '17 at 10:56
  • @kennytm It is an unsustainable hack. Its only one disadvantage - if I have `Vector2D` derived from `VecFloatFix<2>`, it is dangerous when user calls `Vector2D::z` (no compile error). – javaLover Mar 19 '17 at 10:58
  • @javaLover Yes it is a hack, but I don't see how Vector2D is relevant. The union is defined for Vector3D only. – kennytm Mar 19 '17 at 11:00
  • @kennytm sorry, I didn't read you comment carefully. I will test it! – javaLover Mar 19 '17 at 11:01
  • @kennytm I tested, it doesn't solve. http://ideone.com/xMPNHM It can't recognize operator. – javaLover Mar 19 '17 at 11:10
  • 1
    @javaLover: Looking at your username, shouldn't you consider `solve(a.getX(), a.getY(), c.getX(), c.getY());` cutest? – Christian Hackl Mar 19 '17 at 11:10
  • @Christian Hackl I am still struggling to adapt myself to fit in this c++ wonderland. I think it is cool to have `Public Attributes : Real x` from http://www.ogre3d.org/docs/api/1.9/class_ogre_1_1_vector3.html. I usually don't do like this. But for this specific class, the convenience is much more than the annoyance of my broken encapsulation. – javaLover Mar 19 '17 at 11:14
  • 1
    @javaLover: I think your Java background makes you think too much in terms of types. A more suitable philosophy for C++ is based on the original STL ideas which have become so important in the standard library: separate container types from algorithms. Try to not squeeze `Vector3D` and `VecFloatFix` into one class. Aim for generic operations which can operate with both types, *and* with the Ogre vector. Now that would fit into this C++ wonderland! :) Use type traits as helpers to achieve that goal. – Christian Hackl Mar 19 '17 at 11:19
  • @Christian Hackl After staring for some time, I feel there is some great wisdom hiding in your posts (around "generic operations" and "type_traits"). Thank, I will follow the light your provided. XD – javaLover Mar 19 '17 at 11:50

2 Answers2

2

This is by no ways guaranteed to work as it uses implementation-defined and possibly undefined behaviour. A sensible implementation will probably behave as expected though.

template<int constSize> 
class VecFloatFix{
public:
  union {
    float database[constSize];
    struct {
        int x, y, z;
    };
  };
};

This also leaves database public. Don't see a way around this, but no big deal since you provide operator[] anyway.

This assumes constSize >= 3. If you need smaller sizes, this is doable through a bit more hackery. All vectors will have x y and z members but only 3D and above will have them all usable. The 2D vector will have only x and y usable (any use of z is likely to result in an error) and the 1D vector will have just x. Note I refuse to take responsibility for any of the following.

template<int constSize>
class VecFloatFix{
    public:
        union {
            float database[constSize];
            struct {
                float x;
            };
            struct {
                spacer<constSize, 1> sp1;
                typename spacer<constSize, 1>::type y;
            };
            struct {
                spacer<constSize, 2> sp2;
                typename spacer<constSize, 2>::type z;
            };
        };
};

where spacer is defined this way:

template <int N, int M, bool enable>
struct filler;

template <int N, int M>
struct filler<N, M, true>
{
    float _f[M];
    typedef float type;
};

template <int N, int M>
struct filler<N, M, false>
{
    struct nothing {};
    typedef nothing type;
};

template <int N, int M>
struct spacer
{
    filler<N, M, (N>M)> _f;
    typedef typename filler<N, M, (N>M)>::type type;
};

Test drive:

VecFloatFix<4> vec4;
VecFloatFix<3> vec3;
VecFloatFix<2> vec2;
VecFloatFix<1> vec1;

`smoke test`
vec3.database[0] = 42;
vec2.database[1] = 99;
std::cout << vec3.x << std::endl;
std::cout << vec2.y << std::endl;

// make sure `y` aliases `database[1]`
std::cout << & vec2.y << std::endl;
std::cout << & vec2.database[1] << std::endl;

// make sure sizes are as expected
std::cout << sizeof(vec4) << " " << sizeof (vec3) << " " << sizeof(vec2) << " " << sizeof(vec1) << std::endl;
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
2

Here is a version that only exposes x, y, z components for vectors of size 3. Obviously other sizes may also be specialized.

template<int constSize> struct VecFloatStorage
{
    float database[constSize];
};

template<> struct VecFloatStorage<3>
{
    union
    {
        float database[3];
        struct { float x, y, z; };
    };
};

template<int constSize> class VecFloatFix : public VecFloatStorage<constSize>
{
public:
    // Methods go here.
};

I don't know if the standard guarantees struct { float x, y, z; } to have the same memory layout as float data[3], however in practice I am pretty certain that assumption holds.

The GLM library is using a similar trick, except they don't have an array member at all, instead providing an indexing operator that returns (&this->x)[idx].

Joseph Artsimovich
  • 1,499
  • 10
  • 13
  • As n.m. stated, it is implementation-defined (i.e. unreliable). I just found two link describes about "anonymous struct" - http://stackoverflow.com/questions/2253878 (better description) and http://stackoverflow.com/q/13624760. It is sad, because this solution is very beautiful. – javaLover Mar 19 '17 at 13:18