make vector 3D derived from vector ND, need to ret

2019-09-08 04:52发布

问题:

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).

回答1:

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].



回答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;