10
\$\begingroup\$

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
\$\endgroup\$
1
  • \$\begingroup\$ Code updated based on suggestions here \$\endgroup\$
    – Wijagels
    Commented Dec 20, 2016 at 4:37

2 Answers 2

5
\$\begingroup\$

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.

\$\endgroup\$
3
  • \$\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
    Commented 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
    Commented 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
    Commented Dec 20, 2016 at 3:08
4
\$\begingroup\$

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.

\$\endgroup\$
1
  • \$\begingroup\$ Nice, I like the initializer list constructor a lot \$\endgroup\$
    – Wijagels
    Commented Dec 20, 2016 at 3:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.