I'm making a simple geometry library that consists of vectors (in the physics sense, not stl container sense) and transforms. For what I'm doing, a transform is limited to a rotation and a translation. I'm trying to define the types such that improperly applied transforms can be detected at compile time, i.e. all the math is done using Eigen::Matrix3d
and Eigen::Vector3d
, however I'm trying to give semantic meaning to certain definitions of Matrix3d
and Vector3d
so they must meet constraints in order be allowed.
For example if Pj
is a vector P
in coordinate frame j
and Tjk
is a transform that takes a vector expressed in frame j
and expresses it in frame k
then Pk = Tjk * Pj
and Pj = Tkj * Pk
are valid transforms (with Tkj = Tjk.inverse()
) whereas Pj = Tjk * Pk
and Pk = Tkj * Pj
are not. Transforms can also be combined, i.e. in Pm = Tkm * Tjk *Pj
, Tkm*Tjk = Tjm
=> Pm = Tjm* Pj
In this case,Pm = Tjk * Tkm * Pj
(transforms swapped) is erroneous and would be nice if it was caught at compile time.
Here's what I've tried so far:
template <class FromCoord, class ToCoord>
class Transform{
public:
Transform() = default;
Transform(const RotationMatrix& r, const ToCoord& translation):
rotation_{r}, translation_{translation} {}
ToCoord operator*(const FromCoord& v) const {
return FromCoord{rotation_*v + translation_};
}
// template<class ToNextCoord>
// friend Transform<FromCoord, ToNextCoord> operator*(const Transform<ToCoord, ToNextCoord> t2, const Transform<FromCoord, ToCoord>& t1 ){
// return Transform<FromCoord, ToNextCoord> {t2.rotation_ * t1.rotation_, t2 * t1.translation_};
// }
// compute transform inverse
Transform<ToCoord, FromCoord> inv(){
Transform<ToCoord, FromCoord> m_t;
m_t.rotation_ = rotation_.transpose();
m_t.translation_ = rotation_.transpose() * -translation_;
return m_t;
}
// should be private:
Eigen::Matrix3d rotation_;
ToCoord translation_;
};
template<class FromCoord2, class ToCoord3, class FromCoord1, class ToCoord2>
Transform<FromCoord1, ToCoord3> operator*(const Transform<FromCoord2, ToCoord3> t2, const Transform<FromCoord1, ToCoord2>& t1 ){
static_assert(std::is_same_v<ToCoord2, FromCoord2>, "bah this is not right");
return Transform<FromCoord1, ToCoord3> {t2.rotation_ * t1.rotation_, t2 * t1.translation_};
}
struct NedLocal: public Eigen::Vector3d {
using Eigen::Vector3d::Vector3d;
// NedLocal(double x, double y, double z): Eigen::Vector3d(x,y,z) {}
};
struct BodyFrame: public Eigen::Vector3d {using Eigen::Vector3d::Vector3d;};
struct CameraFrame: public Eigen::Vector3d {using Eigen::Vector3d::Vector3d;};
int main(){
CameraFrame ray_cam{};
Transform<CameraFrame, BodyFrame> Tcb{ };
Transform<BodyFrame, NedLocal> Tbi{ };
NedLocal inertial_los = Tbi*Tcb*ray_cam; // ok
NedLocal inertial_los1 = Tcb*ray_cam; // this should generate a compile time error on assignment
NedLocal inertial_los2 = Tbi*ray_cam; // this should generate a compile time error on operator*
}
however in this case, because of the using
statements all of the coordinate frames can be implicitly converted to each other and so no compile errors are given. The static_assert
also doesn't catch it. Removing the using
statement cause other compiler errors
full example is here: https://godbolt.org/z/drPYG3
Is what I'm after possible and if so how would I do this? It would be a bonus if there was a way to use concepts to make friendlier compiler messages.