I needed to make a point class for a term project that involved arbitrary dimensional points. I came up with this, and I know that it works fine, but I'm looking to improve it so I can reuse it in the future. Any criticisms or suggestions are very much appreciated.
#ifndef POINT_HPP
#define POINT_HPP
#include <array>
#include <cmath>
#include <initializer_list>
#include <iostream>
template <size_t D>
struct Point;
template <size_t D>
struct Point {
Point(const std::initializer_list<double>& v) {
size_t i = 0;
for (auto e : v) {
coordinates_[i++] = e;
}
}
explicit Point(const std::array<double, D>& arr) : coordinates_{arr} {}
double operator[](size_t dimension) const { return coordinates_[dimension]; }
Point<D>& operator=(const Point& other) {
this->coordinates_ = other.coordinates_;
return *this;
}
std::array<double, D> coordinates_;
};
/*
* Finds euclidian distance between two Point classes of same dimension
*/
template <size_t D>
double distance(const Point<D>& lhs, const Point<D>& rhs) {
double dist = 0;
for (size_t i = 0; i < D; i++) dist += std::pow(lhs[i] - rhs[i], 2);
return std::sqrt(dist);
}
/*
* Finds euclidian distance between Point and axis
*/
template <size_t D>
double distance(const Point<D>& point, double value, int axis) {
return std::abs(point[axis] - value);
}
/*
* Print the point components of a Point in ascending order.
* Prints in the form {x, y, z} with no newline.
*/
template <size_t D>
std::ostream& operator<<(std::ostream& os, const Point<D>& point) {
os << "{";
for (size_t i = 0; i < D - 1; i++) {
os << point[i] << ", ";
}
os << point[D - 1] << "}";
return os;
}
template <size_t D>
bool operator==(const Point<D>& lhs, const Point<D>& rhs) {
return lhs.coordinates_ == rhs.coordinates_;
}
namespace std {
/*
* Make our Point class hashable
*/
template <size_t D>
struct hash<Point<D>> {
size_t operator()(const Point<D>& x) const {
return hash<array<double, D>>()(x.coordinates_);
}
};
/*
* Also make it swappable for sorting
*/
template <size_t D>
void swap(Point<D>& lhs, Point<D>& rhs) {
swap(lhs.coordinates_, rhs.coordinates_);
}
} // namespace std
#endif // POINT_HPP
-
\$\begingroup\$ Code updated based on suggestions here \$\endgroup\$Wijagels– Wijagels2016年12月20日 04:37:42 +00:00Commented Dec 20, 2016 at 4:37
2 Answers 2
Overall, this seems to be a small but useful template. However, there are a few things that I think might be improved.
Sorting requires more than swap
While the provided swap
function would assist in some kind of sorting, without a comparison operator, we still can't actually sort. This may well be by design, in which case a comment in the code pointing this out would be helpful.
Consider default constructor
There is no simple way at the moment to create a generic templated origin point (all coordinates zero). This makes it a bit more difficult to create something like a distance_from_origin
function that is generic.
Consider projection operator
It's often useful to project, say, a 3D point onto a 2D canvas. This could also likely be made generic.
Do a range check
The distance
function that measures from an axis does no range checking on the axis number and there does not seem to be a good generic way to add one. Consider providing a range checking version.
-
\$\begingroup\$ As for the sorting-point: how would you sort 2D-points or higher dimensional points? It depends upon how you want them sorted. IMO this would rather belong to a collection of utility methods than to the
struct
itself, if anywhere else than the actual application. \$\endgroup\$user82687– user826872016年12月19日 22:36:14 +00:00Commented Dec 19, 2016 at 22:36 -
\$\begingroup\$ @Paul indeed there is no obvious single way to sort such points, but the existing code provides for a swap method for sorting. It seemed to me that it was worth pointing out that more than just a swap would be required for sorting. \$\endgroup\$Edward– Edward2016年12月19日 22:45:51 +00:00Commented Dec 19, 2016 at 22:45
-
\$\begingroup\$ @Paul: I'm using this point class to construct k-d trees, hence the swap function. The tree is sorted based on an axis at each level, which is why I had the distance with the axis function. \$\endgroup\$Wijagels– Wijagels2016年12月20日 03:08:23 +00:00Commented Dec 20, 2016 at 3:08
Very good. Yet making it more generic is not hard:
template<typename T, size_t D>
class PointExt {
public:
PointExt(std::initializer_list<T> v) {
std::copy(v.begin(), v.end(), coordinates_.begin());
}
explicit PointExt(const std::array<T, D>& arr) : coordinates_{arr} {}
T operator[](size_t dimension) { return coordinates_[dimension]; }
const T operator[](size_t dimension) const { return coordinates_[dimension]; }
private:
std::array<T, D> coordinates_;
};
Now you can compute, say, a Manhattan distance of two points with different component types (by returning the distance in the most precise type):
template<typename T1, typename T2, size_t D>
decltype(T1{} + T2{}) manhattan_distance(const PointExt<T1, D>& lhs,
const PointExt<T2, D>& rhs) {
auto sum = decltype(T1{} + T2{}){};
for (size_t i = 0; i != D; ++i) {
sum += std::abs(lhs[i] - rhs[i]);
}
return sum;
}
In the output overload, you can write one line more succintly:
template<typename T, size_t D>
std::ostream& operator<<(std::ostream& os, const PointExt<T, D>& point) {
os << "(";
for (size_t i = 0; i < D - 1; ++i) {
os << point[i] << ", ";
}
return os << point[D - 1] << ")";
}
Hope that helps.
-
\$\begingroup\$ Nice, I like the initializer list constructor a lot \$\endgroup\$Wijagels– Wijagels2016年12月20日 03:14:41 +00:00Commented Dec 20, 2016 at 3:14