1

I'm dealing with a lot of coordinate data, think predefined library types

struct Point3d { double x,y,z; };

and the like from Eigen and OpenCV.

Now, the coordinates of each point are expressed in some frame of reference. I'd like the type system to track the frame in which each point is expressed. Something along the lines of

enum Frames { frame1, frame2 };

using Point_1 = TagWrapper<Point3d, frame1>;
using Point_2 = TagWrapper<Point3d, frame2>;

Point3d untagged_pt = ...;
Point_1 pt1 = ...;
Point_2 pt2 = ...;
Transform<frame1, frame2> tf_1_to_2 = ...;  // from frame1 to frame2

// Compile time error, pt1 and pt2 are in different frames
auto pt3 = pt1 + pt2; 

// Ok!, and typeof(pt4) == Point_2
auto pt4 = (tf_1_to_2 * pt1) + pt2; 

// Compile time error, pt2 is not in frame1
auto pt5 = tf_1_to_2 * pt2;

// Ok!, and typeof(pt5) == Point_1
auto pt5 = untagged_pt + pt1;

Preferably I could wrap any type with any "tag" to make it a tagged type. Then all similarly tagged types behave as their untagged types when used with each other, but mixing objects with different tags should be a compile time error. I suppose it would also make sense that the result of operations between an untagged and tagged type becomes tagged.

This is similar to units, but I'd like to turn anything into multiple kinds of mutually exclusive "units". So a TagWrapper<Person, type1> has the interface of Person, but won't interact with a TagWrapper<Person, type2>, for example.

Justin
  • 24,288
  • 12
  • 92
  • 142
daemacles
  • 302
  • 1
  • 6
  • This is a little bit unclear. It seems to me like you are looking for something like a units library, where you could have `1m + 1m = 2m`, but `1m + 1N` is a compile error. Except it seems you want to restrict it so that it has to be exactly the same "unit", so you couldn't do something like `1m / 1s = 1 m/s` – Justin Jul 28 '17 at 20:18
  • Units is a good way to think about it, but I'd like to turn anything into multiple kinds of mutually exclusive "units". So a `TagWrapper` has the interface of `Person`, but won't interact with a `TagWrapper`, for example. – daemacles Jul 29 '17 at 03:44
  • You can't replicate the interface of `Person` entirely. You have to manually write out the interfaces that you want to support (so arithmetic operators, etc). What you can do beyond that is add a `get()` member function that gets the underlying type. In a bit, I'll come back here and answer your question – Justin Jul 29 '17 at 03:53

1 Answers1

1

To make separate types for different frames, just take it as a template parameter. We'd then need to define whatever interface we want over the type. Here's an example of something you could write:

#include <utility> // for std::move
#include <iterator> // for std::begin, std::end

template <typename T, typename Tag, Tag kTag>
class TagWrapper
{
    T value_;

public:
    TagWrapper(T value)
        : value_{ std::move(value) }
    {}

    // Note: This will allow you to add a T to a TagWrapper<T, ...>
    // However, if T had an implicit constructor, you wouldn't be able
    // to use that. If you wanted to support it, you'd have to 3x the operator overloads
    // you implement. That is, you'd also need:
    //
    // friend auto operator+(T const& lhs, TagWrapper<T, Tag, kTag> const& rhs);
    // friend auto operator+(TagWrapper<T, Tag, kTag> const& lhs, T const& rhs);
    friend auto operator+(TagWrapper<T, Tag, kTag> const& lhs, TagWrapper<T, Tag, kTag> const& rhs)
    {
        return TagWrapper<T, Tag, kTag>{ lhs.value_ + rhs.value_ };
    }

    friend auto operator*(TagWrapper<T, Tag, kTag> const& lhs, TagWrapper<T, Tag, kTag> const& rhs)
    {
        return TagWrapper<T>{ lhs.value_ + rhs.value_ };
    }

    // the other arithmetic operators...

    // You'd also want to do comparison operators

    // Because it's impossible to completely delegate member functions on to
    // everything that T can do, provide accessors to T. You may also prefer
    // to define conversions, explicit or implicit:
    //
    // operator T const&() const { return value_; }
    // explicit operator T const&() const { return value_; }
    T const& get() const { return value_; }
    T& get() { return value_; }

    // As an example of generally wrapping, you could do this:
    auto begin() { return std::begin(value_); }
    auto begin() const { return std::begin(value_); }
    auto end() { return std::end(value_); }
    auto end() const { return std::end(value_); }
    // This would make it so that if your type T was a "collection", then
    // TagWrapper<T, ...> is as well. You could even use TagWrapper<T, ...>
    // in a for-each loop

    // Provide some way to see what the tag is. You may or may not want to expose this
    static Tag tag = kTag;
};

If you wanted the exact syntax you wanted in the question, you could replace template <typename T, typename Tag, Tag kTag> with template <typename T, Frames frame> and make the necessary changes, or you could use this type alias:

template <typename T, Frames frame>
using MyTagWrapper = TagWrapper<T, Frames, frame>;

With this, mixing two tagged types would result in a compile error, but mixing a tagged type and an untagged one would convert to the tagged one. All that's left is defining conversion functions between tagged types, which is pretty easy to do:

MyTagWrapper<T, frame1> to_frame1(MyTagWrapper<T, frame2> const&);

And then this:

auto pt4 = (tf_1_to_2 * pt1) + pt2;

becomes this:

auto pt4 = to_frame1(pt1) + pt2;
Justin
  • 24,288
  • 12
  • 92
  • 142
  • 1
    // Because it's impossible to completely delegate member functions on to // everything that T can do, provide accessors to T. Oh well... Here's waiting for `reflexpr` and `operator$` and ilk. – daemacles Aug 01 '17 at 01:47