2

I have a bunch of vector classes. I have a 2D point vec2_t, a 3D point vec3_t and a 4D point vec4_t (you often want these when you have do graphics; this is graphics code, but the question has a generic C++ flavour).

As it is now, I have vec2_t declaring two members x and y; vec3_t subclasses vec2_t and has a third member z; vec4_t subclasses vec3_t and adds a w member.

I have a lot of near-duplicate code for operator overloading computing things like distances, cross products, multiplication by a matrix and so on.

I've had a few bugs where things have been sliced when I've missed to declare an operator explicitly for the subclass and so on. And the duplication bugs me.

Additionally, I want to access these members as an array too; this would be useful for some OpenGL functions that have array parameters.

I imagine that perhaps with a vec_t<int dimensions> template I can make my vector classes without subclassing. However, this introduces two problems:

  1. How do you have a variable number of members that are also array entries, and ensure they align? I don't want to lose my named members; vec.x is far nicer than vec.d[0] or whatever imo and I'd like to keep it if possible
  2. How do you have lots of the more expensive methods in a CPP source file instead of the header file when you take the templating route?

One approach is this:

struct vec_t {
   float data[3];
   float& x;
   float& y;
   float& z;
   vec_t(): x(data[0]), y(data[1]), z(data[2]) {}
};

Here, it correctly aliases the array members with names, but the compiler I've tested with (GCC) doesn't seem to work out they are just aliases and so the class size is rather large (for something I might have an array of, and want to pass e.g. as a VBO; so size is a big deal) and how would you template-parameterise it so only the vec4_t had a w member?)

Will
  • 73,905
  • 40
  • 169
  • 246
  • 1
    If you don't mind some compiler specificity, you can use one of the approaches shown in [this question](http://stackoverflow.com/questions/1537964/visual-c-equivalent-of-gccs-attribute-packed) to ensure packing of the data-members without using an array. – Björn Pollex Dec 16 '11 at 13:32

6 Answers6

2

A possible solution (I think).

main.cpp:

#include <iostream>
#include "extern.h"

template <int S>
struct vec_t_impl
{
    int values[S];
    bool operator>(const vec_t_impl<S>& a_v) const
    {
        return array_greater_than(values, a_v.values, S);
    }
    void print() { print_array(values, S); }

    virtual ~vec_t_impl() {}
};

struct vec_t2 : vec_t_impl<2>
{
    vec_t2() : x(values[0]), y(values[1]) {}
    int& x;
    int& y;
};

struct vec_t3 : vec_t_impl<3>
{
    vec_t3() : x(values[0]), y(values[1]), z(values[2]) {}
    int& x;
    int& y;
    int& z;
};

int main(int a_argc, char** a_argv)
{
    vec_t3 a;
    a.x = 5;
    a.y = 7;
    a.z = 20;

    vec_t3 b;
    b.x = 5;
    b.y = 7;
    b.z = 15;

    a.print();
    b.print();

    cout << (a > b) << "\n";

    return 0;
}

extern.h:

extern bool array_greater_than(const int* a1, const int* a2, const size_t size);
extern void print_array(const int* a1, const size_t size);

extern.cpp:

#include <iostream>

bool array_greater_than(const int* a1, const int* a2, const size_t size)
{
    for (size_t i = 0; i < size; i++)
    {
        if (*(a1 + i) > *(a2 + i))
        {
            return true;
        }
    }
    return false;
}

void print_array(const int* a1, const size_t size)
{
    for (size_t i = 0; i < size; i++)
    {
        if (i > 0) cout << ", ";
        std::cout << *(a1 + i);
    }
    std::cout << '\n';
}

EDIT:

In an attempt to address the size issue you could change the member reference variables to member functions that return a reference.

struct vec_t2 : vec_t_impl<2>
{
    int& x() { return values[0]; }
    int& y() { return values[1]; }
};

Downside to this is slightly odd code:

vec_t2 a;
a.x() = 5;
a.y() = 7;
hmjd
  • 120,187
  • 20
  • 207
  • 252
1

Note: Updated and improved the code a lot.

The following code uses a macro to keep the code clean and partial specialization to provide the members. It relies heavily on inheritence, but that makes it very easy to extend it to arbitary dimensions. It's also intended to be as generic as possible, that's why the underlying type is a template parameter:

// forward declaration, needed for the partial specializations
template<unsigned, class> class vec;

namespace vec_detail{
// actual implementation of the member functions and by_name type
// partial specializations do all the dirty work
template<class Underlying, unsigned Dim, unsigned ActualDim = Dim>
struct by_name_impl;

// ultimate base for convenience
// this allows the macro to work generically
template<class Underlying, unsigned Dim>
struct by_name_impl<Underlying, 0, Dim>
{ struct by_name_type{}; };

// clean code after the macro
// only need to change this if the implementation changes
#define GENERATE_BY_NAME(MEMBER, CUR_DIM) \
    template<class Underlying, unsigned Dim> \
    struct by_name_impl<Underlying, CUR_DIM, Dim> \
        : public by_name_impl<Underlying, CUR_DIM - 1, Dim> \
    { \
    private: \
        typedef vec<Dim, Underlying> vec_type; \
        typedef vec_type& vec_ref; \
        typedef vec_type const& vec_cref; \
        typedef by_name_impl<Underlying, CUR_DIM - 1, Dim> base; \
    protected: \
        struct by_name_type : base::by_name_type { Underlying MEMBER; }; \
        \
    public: \
        Underlying& MEMBER(){ \
            return static_cast<vec_ref>(*this).member.by_name.MEMBER; \
        } \
        Underlying const& MEMBER() const{ \
            return static_cast<vec_cref>(*this).member.by_name.MEMBER; \
        } \
    }

GENERATE_BY_NAME(x, 1);
GENERATE_BY_NAME(y, 2);
GENERATE_BY_NAME(z, 3);
GENERATE_BY_NAME(w, 4);

// we don't want no pollution
#undef GENERATE_BY_NAME
} // vec_detail::

template<unsigned Dim, class Underlying = int>
class vec
    : public vec_detail::by_name_impl<Underlying, Dim>
{
public:
    typedef Underlying underlying_type;

    underlying_type& operator[](int idx){
        return member.as_array[idx];
    }

    underlying_type const& operator[](int idx) const{
        return member.as_array[idx];
    }

private:
    typedef vec_detail::by_name_impl<Underlying, Dim> base;
    friend struct vec_detail::by_name_impl<Underlying, Dim>;
    typedef typename base::by_name_type by_name_type;

    union{
        by_name_type by_name;
        underlying_type as_array[Dim];
    } member;
};

Usage:

#include <iostream>

int main(){
    typedef vec<4, int> vec4i;
    // If this assert triggers, switch to a better compiler
    static_assert(sizeof(vec4i) == sizeof(int) * 4, "Crappy compiler!");
    vec4i f;
    f.w() = 5;
    std::cout << f[3] << '\n';
}

Of course you can make the union public, if you want to, but I think accessing the members through the function is better.

Note: The above code compiles cleanly without any warnings on MSVC10, GCC 4.4.5 and Clang 3.1 with -Wall -Wextra (/W4 for MSVC) and -std=c++0x (only for static_assert).

Xeo
  • 129,499
  • 52
  • 291
  • 397
  • How can you template parameterise it? – Will Dec 16 '11 at 14:12
  • @Will: Cleaned and updated the post again. Genericity ftw. :) Though at the moment it's not possible to convert a higher-dimensioned `vec` to a lower-dimensioned one. I'll work on that too sometime tomorrow. – Xeo Dec 17 '11 at 02:53
0

This is yet another approach. The following works on gcc C++11:

#include <stdio.h>

struct Vec4
{  
  union
  {
    float raw[4];
    struct {
      float x;
      float y;
      float z;
      float w;
    };
  };
};

int main()
{

  Vec4 v = { 1.f, 2.f, 3.f, 4.f };
  
  printf("%.2f, %.2f, %.2f, %.2f\n", v.x, v.y, v.z, v.w);
  printf("%.2f, %.2f, %.2f, %.2f\n", v.raw[0], v.raw[1], v.raw[2], v.raw[3]);

  return 0;
}
zzxoto
  • 62
  • 6
0

This would be one way to do it:

#include<cstdio>

class vec2_t{
public:
    float x, y;
    float& operator[](int idx){ return *(&x + idx); }
};

class vec3_t : public vec2_t{
public:
    float z;
};

Edit: @aix is right in saying that it's non-standard and could cause problems. Perhaps a more appropriate solution would then be:

class vec3_t{
public:
    float x, y, z;

    float& operator[](int idx){

        static vec3_t v;
        static int offsets[] = {
            ((char*) &(v.x)) - ((char*)&v),
            ((char*) &(v.y)) - ((char*)&v),
            ((char*) &(v.z)) - ((char*)&v)};

        return *( (float*) ((char*)this+offsets[idx]));
    }
};

Edit #2: I have an alternative, where it's possible to only write your operators once, and not end up with a bigger class, like so:

#include <cstdio>
#include <cmath>

template<int k>
struct vec{

};

template<int k>
float abs(vec<k> const&v){
    float a = 0;
    for (int i=0;i<k;i++)
        a += v[i]*v[i];
    return sqrt(a);
}

template<int u>
vec<u> operator+(vec<u> const&a, vec<u> const&b){
    vec<u> result = a;
    result += b;
    return result;
}

template<int u>
vec<u>& operator+=(vec<u> &a, vec<u> const&b){
    for (int i=0;i<u;i++)
        a[i] = a[i] + b[i];
    return a;
}

template<int u>
vec<u> operator-(vec<u> const&a, vec<u> const&b){
    vec<u> result;
    for (int i=0;i<u;i++)
        result[i] = a[i] - b[i];
    return result;
}

template<>
struct vec<2>{
    float x;
    float y;
    vec(float x=0, float y=0):x(x), y(y){}
    float& operator[](int idx){
        return idx?y:x;
    }
    float operator[](int idx) const{
        return idx?y:x;
    }
};

template<>
struct vec<3>{
    float x;
    float y;
    float z;

    vec(float x=0, float y=0,float z=0):x(x), y(y),z(z){}
    float& operator[](int idx){
        return (idx==2)?z:(idx==1)?y:x;
    }
    float operator[](int idx) const{
        return (idx==2)?z:(idx==1)?y:x;
    }
};

There are some problems, though:

1) I don't know how you'd go around defining member functions without having to write them (or at least some sort of stub) more than once.

2) It relies on compiler optimizations. I looked at the output from g++ -O3 -S and it seems that the loop gets unrolled and the ?:s get replaced with the proper field accesses. The question is, would this still be handled properly in a real context, say within an algorithm?

Vlad
  • 18,195
  • 4
  • 41
  • 71
  • 2
    Clever as this is, surely it relies on undefined behaviour? There could be padding between `x` and `y`, not to mention the assumptions around the location of `z` in memory. – NPE Dec 16 '11 at 11:58
  • Its basically the subclasaing approach - how to dedup operators and avoid slicing? – Will Dec 16 '11 at 12:14
  • I much prefer your second edit; by mimicking an array with operator[], how do you use it safely as an array to pass to a function wanting an array? – Will Dec 16 '11 at 14:26
  • 1
    Unfortunately, I don't think you can, not if it wants a `float*`. It would work however with a template function which only requires that its argument be indexable. – Vlad Dec 16 '11 at 14:41
0

A simple solution might be the best here:

struct Type
{
    enum { x, y };
    int values[2];
};

Type t;
if (t.values[0] == t.values[Type::x])
    cout << "Good";

You can also do something like this:

struct Type
{
    int values[2];

    int x() const {
        return values[0];
    }

    void x(int i) {
        values[0] = i;
    }
};
Paul Manta
  • 30,618
  • 31
  • 128
  • 208
0

If you do not want to write it yourself, you may check some of the libraries suggested on:

C++ Vector Math and OpenGL compatable

If you use one specific compiler, you may use non standard methods, like packing information or nameless structs (Visual Studio):

union Vec3
{
  struct {double x, y, z;};
  double v[3];
};

On the other hand, casting several member variables to an array seems dangerous because the compiler may change the class layout.

So the logic solution seems to have one array and using methods to access that array. For example:

template<size_t D>
class  Vec
{
private: 
  float data[D];

public:  // Constants
  static const size_t num_coords = D;

public:  // Coordinate Accessors
  float& x()             { return data[0]; }
  const float& x() const { return data[0]; }
  float& y()             { static_assert(D>1, "Invalid y()"); return data[1]; }
  const float& y() const { static_assert(D>1, "Invalid y()"); return data[1]; }
  float& z()             { static_assert(D>2, "Invalid z()"); return data[2]; }
  const float& z() const { static_assert(D>2, "Invalid z()"); return data[2]; }

public: // Vector accessors
  float& operator[](size_t index) {return data[index];}
  const float& operator[](size_t index) const {return data[index];}

public:  // Constructor
  Vec() {
    memset(data, 0, sizeof(data));
  }

public:  // Explicit conversion
  template<size_t D2>
  explicit Vec(const Vec<D2> &other) {
    memset(data, 0, sizeof(data));
    memcpy(data, other.data, std::min(D, D2));
  }
};

Using the above class, you may access the member array using the [] operator, coordinates using the accessor methods x(), y(), z(). Slicing is prevented using explicit conversion constructors. It disables the use of the accessors for lower dimensions using static_assert. If you are not using C++11, you may use Boost.StaticAssert

You can also templatized your methods. You can use for in order to extend them to N dimensions or use recursive calls. For example, in order to compute the square sum:

template<size_t D>
struct Detail
{
  template<size_t C>
  static float sqr_sum(const Vec<D> &v) {
    return v[C]*v[C] + sqr_sum<C-1>(v);
  }

  template<>
  static float sqr_sum<0>(const Vec<D> &v) {
    return v[0]*v[0];
  }
};

template<size_t D>
float sqr_sum(const Vec<D> &v) {
  return Detail<D>::sqr_sum<D-1>(v);
}

The above code can be used:

int main()
{ 
  Vec<3> a;
  a.x() = 2;
  a.y() = 3;
  std::cout << a[0] << " " << a[1] << std::endl;
  std::cout << sqr_sum(a) << std::endl;;

  return 0;
} 

In order to prevent template bloat, you may code your templated methods on a cpp and instantiated them for D=1, 2, 3, 4.

Community
  • 1
  • 1
J. Calleja
  • 4,855
  • 2
  • 33
  • 54