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
-
Choose
quantity_type(a coordinate value) andtf_data_type(a transformation between coordinate values). They must be distinct,std::semiregular, and default-constructible without throwing. -
Implement
invert_tf(tf)andcompose_tf(after, first)— the inverse and the group composition oftf_data_type. -
Implement
transform_coords(tf, qty)— the action of a transformation on a coordinate. This one must benoexcept. -
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.