I have the simple data structure describing 2d point in cartesian coordinate system, like below.
struct CartPoint
{
double x;
double y;
}
and second strucutre, representing 2d point in polar coordinate system
struct PolarPoint
{
double r;
double alpha;
}
and also two functions allowing me to translate from one representation to second one:
void translate(const CartPoint& from, PolarPoint& to) { ... };
void translate(const PolarPoint& from, CartPoint& to) { ... };
I would like to create object (let me call it PointContainer), that allows me to store cartesian 2D points in one vector, but accessing those in either cartesian or polar representation (based on compile-time decision). I was thinking of a class exposing two types of non-const iterators, one for every representation. However I could not find anywhere such a solution and i am not sure whether it is a good idea. I would like to use it like that:
void fillVectorWithCartPts(std::vector<CartPoint>& points)
{
// fills points-vector with 2d cartesian points
...
};
int main()
{
std::vector<CartPoint> pts{};
fillVectorWithCartPts(pts);
PointContainer pc{pts};
// dummy logic representing use possibilities
for (CartPoint& _pt : pc.GetIterator<CartPoint>())
{
_pt = CartPoint{1.0, 2.0} // modifies points in data via cartesian representation
}
// or
for (PolarPoint& _pt : pc.GetIterator<PolarPoint>())
{
_pt = PolarPoint{3.0, 4.0} // modifies points in data via polar representation
}
// after modification i can retrive vector in selected representation
std::vector<PolarPoint> polarRes = pc.Retrive<PolarPoint>();
std::vector<CartPoint> cartRes = pc.Retrive<CartPoint>();
return 0;
}
I will be very grateful for any suggestion on design of such a class or proposing other solutions to solve the issue of double representation need for the same data.
CodePudding user response:
It's likely the most idiomatic to create an intermediary class that can be viewed as either CartPoint
or PolarPoint
. You can do this multiple ways: containment and getters, or inheritance. Then you can store a vector of these points.
CartPoint toCartPoint(const PolarPoint& pp) { ... }
CartPoint toPolarPoint(const CartPoint& cp) { ... }
class GenericPoint {
public:
GenericPoint(CartPoint cp)
: cp(cp), pp(toPolarPoint(cp)) {}
GenericPoint(PolarPoint pp)
: cp(toCartPoint(pp)), pp(pp) {}
GenericPoint(const GenericPoint& gp)
: cp(gp.cp), pp(gp.pp) {}
operator const CartPoint&() const { return cp; }
operator const PolarPoint&() const { return pp; }
const CartPoint& getCartPoint() const { return cp; }
const PolarPoint& getPolarPoint() const { return pp; }
GenericPoint& operator=(const GenericPoint& rhs)
{
cp = rhs.cp;
pp = rhs.pp;
}
GenericPoint& operator=(const CartPoint& cp_in)
{
cp = cp_in;
pp = toPolarPoint(cp_in);
}
GenericPoint& operator=(const PolarPoint& pp_in)
{
cp = toCartPoint(pp_in);
pp = pp_in;
}
private:
CartPoint cp;
PolarPoint pp;
};
CodePudding user response:
I suggest selecting one of them and only convert to the other when absolutely needed. If you select to store CartPoint
s internally, you could convert back and forth beween CartPoint
s and PolarPoint
s if PolarPoints
are needed for some calculations. You'd store std::vector<CartPoint>
in your container though.
Just keep in mind that floating point math is tricky and you may not get the same result if you do calculations in the PolarPoint
domain and convert to CartPoint
as if you did the calculations in the CartPoint
domain.
It could nevertheless look something like this:
struct CartPoint {
double x = 0.;
double y = 0.;
CartPoint() = default;
CartPoint(double X, double Y) : x{X}, y{Y} {}
// convert a PolarPoint to a CartPoint
CartPoint(const PolarPoint& pp) :
CartPoint(pp.r * std::cos(pp.alpha),
pp.r * std::sin(pp.alpha))
{}
// comparisons
auto operator<=>(const CartPoint& rhs) const = default;
// return a Length proxy object in case the length is only used for comparisons
Length length() const { return {x*x y*y}; }
// convert to a PolarPoint if needed by some function
operator PolarPoint () const {
return {std::sqrt(x*x y*y), std::atan2(y, x)};
}
};
The Length
proxy object is there to save us from calls to std::sqrt
when we only want to compare the lengths of two CartPoint
s:
struct Length {
double len_squared;
// don't use sqrt if only comparing lengths
auto operator<=>(const Length& rhs) const {
return len_squared <=> rhs.len_squared;
}
// comparisons with actual doubles
auto operator<=>(double l) const {
return len_squared <=> l*l; // square the supplied value
}
// convert to double only when the actual length is needed
operator double() const { return std::sqrt(len_squared); }
};