Vybz Vybz - 4 months ago 18
Linux Question

Make a c++ class work with generic user defined inputs

I feel like this question must have been asked before but I couldn't find an answer from poking around on google. If it has please direct me to a link and I will remove this post.

Consider this minimal example that represents a larger problem I have. Say I created a simple "Point" and "Printer" class like so:

class Point {
public:
double x, y;

Point() {x = y = 0;}
Point(double x, double y) {
this->x = x; this->y = y;
}
};

template<typename T>
class Printer {
public:
T* mData;
int mSize;

// Constructor
Printer(std::vector<T> &input) {
mData = &input[0];
mSize = input.size();
}

// Simple Print function
void Print() {
printf(" - Showing %d items\n", mSize);
for (int i = 0; i < mSize; i++) {
const T &item = mData[i];
printf(" - Item %d: (%lf, %lf)\n", i, item.x, item.y);
}
}
};


I could use the printer class like this:

std::vector<Point> points; // fill the vector, and then...
Printer<Point> pointsPrinter(points); pointsPrinter.Print();


Now say someone else comes along and wants to use the Printer class with there own "Point" class declared like so:

class Pnt {
public:
double mX, mY;
// other stuff
};


If they try to do this:

vector<Pnt> pnts; // Fill the pnts, and then...
Printer<Pnt> pntsPrinter(pnts);
pntsPrinter.Print(); // COMPILE ERROR HERE!


Obviously this will fail because Pnt has no x or y members. Does there exist a way I can rewrite the Printer class to work with all generic user types? What I DONT want to do is copy a Pnt vector into a Points vector.

EDIT:

The only way I can think to make this work would be to pass in functions pointers. Something like this:

template<typename T>
class Printer {
public:

T* mData;
int mSize;
double* (*mXFunc) (T*);
double* (*mYFunc) (T*);

Printer(std::vector<T> &input,
double* (*xFunc) (T*),
double* (*yFunc) (T*))
{
mData = &input[0];
mSize = input.size();
mXFunc = xFunc;
mYFunc = yFunc;
}

void Print() {
printf(" - Showing %d items\n", mSize);
for (int i = 0; i < mSize; i++) {
T &item = mData[i];
printf(" - Item %d: (%lf, %lf)\n", i, *mXFunc(&item), *mYFunc(&item));
}

}
};

// Could then use it like so
inline double* getXPointVal(Point *point) {return &point->x;}
inline double* getYPointVal(Point *point) {return &point->y;}
inline double* getXPntVal(Pnt *point) {return &point->mX;}
inline double* getYPntVal(Pnt *point) {return &point->mY;}

Printer<Pnt> pntPrinter(pnts, getXPntVal, getYPntVal);
Printer<Point> pointsPrinter(points, getXPointVal, getYPointVal);

pntPrinter.Print();
pointsPrinter.Print();


The problem with this is that it looks ugly and also possibly introduces the function call overhead. But I guess the function call overhead would get compiled away? I was hoping a more elegant solution existed...

Answer

You could define free (non-member) functions for each Point class you want to use. The advantage of this is that free functions can be defined later, without making changes to existing classes.

Example:

namespace A {
   class Point {
   public:
      Point (int x, int y) : x_(x), y_(y) {}
      int getX () const { return x_; }
      int getY () const { return y_; }
   private:
      int x_, y_;
   };

   // in addition, we provide free functions
   int getX (Point const & p) { return p.getX(); }
   int getY (Point const & p) { return p.getY(); }
}

namespace B {
   class Pnt {
   public:
      Pnt (int x, int y) : x_(x), y_(y) {}
      int get_x () const { return x_; }
      int get_y () const { return y_; }
   private:
      int x_, y_;
   };

   // Pnt does not have free functions, and suppose we
   //   do not want to add anything in namespace B
}

namespace PointHelpers {
   // free functions for Pnt
   int getX (Pnt const & p) { return p.get_x (); }
   int getY (Pnt const & p) { return p.get_y (); }
}

// now we can write
template <class PointTy>
void printPoint (PointTy const & p) {
   using PointHelpers::getX;
   using PointHelpers::getY;
   std::cout << getX (p) << "/" << getY (p) << std::endl;
}

A::Point p1 (2,3);
B::Pnt p2 (4,5);
printPoint (p1);
printPoint (p2);

If the free functions live in the same namespace as the corresponding class, they will be found by argument-dependent name lookup. If you do not want to add anything in that namespace, create a helper namespace and add the free functions there. Then bring them into scope by using declarations.

This approach is similar to what the STL does for begin and end, for instance.