Forceflow / libmorton

C++ header-only library with methods to efficiently encode/decode Morton codes in/from 2D/3D coordinates
MIT License
596 stars 71 forks source link

A better naming system using tag dispatch #41

Open shohirose opened 4 years ago

shohirose commented 4 years ago

Hi,

You mentioned in TODO that a better naming system is required. I solved the issue by using a combination of tag dispatch, function overloading, and partial specialization of template classes. Please see the implementation in shohirose/morton for details.

A brief overview of my idea is the following:

namespace tag {

// Tags representing each implementation
struct bmi {};
struct preshifted_lookup_table {};
struct lookup_table {};

} // namespace tag

namespace detail {

template <typename MortonCode, typename Coordinate, typename Tag>
class morton2d {};

// Partial specialization of morton2d for tag::bmi
template <typename MortonCode, typename Coordinate>
class morton2d<MortonCode, Coordinate, tag::bmi> {
 public:
  static MortonCode encode(const Coordinate x, const Coordinate y) noexcept {
    // ...
  }
  static void decode(const MortonCode m, Coordinate& x, Coordinate& y) noexcept {
    // ...
  }
};

// Partial specialization of morton2d for tag::preshifted_lookup_table
template <typename MortonCode, typename Coordinate>
class morton2d<MortonCode, Coordinate, tag::preshifted_lookup_table> {
  // ...
};

// Partial specialization of morton2d for tag::lookup_table
template <typename MortonCode, typename Coordinate>
class morton2d<MortonCode, Coordinate, tag::lookup_table> {
  // ...
};

} // namespace detail

// Switch implementation by using a tag dispatch technique.
template <typename Tag>
inline uint_fast32_t encode(const uint_fast16_t x, const uint_fast16_t y, Tag) noexcept {
  return detail::morton2d<uint_fast32_t, uint_fast16_t, Tag>::encode(x, y);
}

template <typename Tag>
inline void decode(const uint_fast32_t m, uint_fast16_t& x, uint_fast16_t& y, Tag) noexcept {
  detail::morton2d<uint_fast32_t, uint_fast16_t, Tag>::decode(x, y);
}

Then, we can specify an implementation through a uniform API:

uint_fast16_t x = // ...
uint_fast16_t y = // ...

// Encode using BMI instruction set (if available)
const auto m1 = encode(x, y, tag::bmi{});
// Encode using preshifted lookup table
const auto m2 = encode(x, y, tag::preshifted_lookup_table{});
// Encode using lookup table
const auto m3 = encode(x, y, tag::lookup_table{});

I hope this might help you.

Forceflow commented 4 years ago

This is some C++-fu I'm not familiar with, but definitely looks interesting! Thanks man. I will look into integrating this in a next version