How to define a new coordinate-system traits type

You want to introduce a coordinate space that the library does not ship with — a new algebra of coordinates and transformations — and have all the generic infrastructure (coordsys, coord_value, coord_cloud, mapCS(), coord_tf) work with it. The type you need to write satisfies coordsys_traits.

A traits type is a small static-interface struct: two using declarations and three static functions. The work is mostly in deciding what your coordinates and transformations are.

The recipe

  1. Choose quantity_type (a coordinate value) and tf_data_type (a transformation between coordinate values). They must be distinct, std::semiregular, and default-constructible without throwing.

  2. Implement invert_tf(tf) and compose_tf(after, first) — the inverse and the group composition of tf_data_type.

  3. Implement transform_coords(tf, qty) — the action of a transformation on a coordinate. This one must be noexcept.

  4. Validate the type with static_assert(nin::coordsys_traits<my_traits>).

Step 1 — Pick the two types

struct my_traits
{
    using quantity_type = my_position;     // e.g. a 3-vector of lengths
    using tf_data_type  = my_transform;    // e.g. a translation + rotation
    // …
};

quantity_type and tf_data_type must be different types so that a transformation cannot be silently passed where a coordinate is expected and vice versa. Both must be std::semiregular (copyable, default-constructible, equality-comparable) and their default constructors must be noexcept — the library relies on cheap value initialisation throughout coordsys and coord_value.

Step 2 — Inverse and composition

These two static functions encode the group structure of tf_data_type:

static tf_data_type invert_tf(tf_data_type const& tf);

static tf_data_type compose_tf(tf_data_type const& after,
                               tf_data_type const& first);

The composition convention is apply first, then after — the same direction mathematicians write (after ∘ first)(x) = after(first(x)). The library relies on this order: coordsys::tf_from_WCS() is defined as invert_tf(tf_to_WCS()), and mapCS() builds its result by composing the to-WCS leg of one side with the from-WCS leg of the other.

Neither function has to be noexcept, but both are typically pure and constexpr when the underlying types allow it.

Step 3 — The action

static quantity_type transform_coords(tf_data_type  const& tf,
                                      quantity_type const& qty) noexcept;

The concept requires noexcept here so that coord_tf::operator() and coord_value::map_to() can themselves be noexcept. The return type only has to be convertible_to<quantity_type>, which lets implementations return an expression-template proxy where useful.

Step 4 — Validate

Place the static_assert immediately after the definition so a contract violation is flagged at the traits type itself, not on first use:

static_assert(nin::coordsys_traits<my_traits>);

Full skeleton

struct my_traits
{
    using quantity_type = my_position;
    using tf_data_type  = my_transform;

    static tf_data_type  invert_tf  (tf_data_type const& tf);
    static tf_data_type  compose_tf (tf_data_type const& after,
                                     tf_data_type const& first);
    static quantity_type transform_coords (tf_data_type  const& tf,
                                           quantity_type const& qty) noexcept;
};

static_assert(nin::coordsys_traits<my_traits>);

Once the static_assert passes, coordsys<my_traits>, coord_value<my_traits>, coord_cloud<my_traits>, mapCS() and coord_tf all work with my_traits automatically — no further registration step is needed.

Worked examples to copy from

In-tree: R3::position_coordsys_traits and R3::orientation_coordsys_traits in src/pos/R3.c++. Each one forwards invert_tf, compose_tf and transform_coords to existing free functions and operators on its tf_data_type, which is the lowest-effort way to satisfy the concept when the underlying types already supply those operations.