如何包装对象,使它们成为无法交互的独立类型?

How can I wrap objects so they are separate types that cannot interact?

我正在处理大量坐标数据,考虑预定义的库类型

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

等来自 Eigen 和 OpenCV。

现在,每个点的坐标都在一些参考系中表示。我希望类型系统跟踪表达每个点的框架。类似于

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;

最好我可以用 "tag" 包装任何类型,使其成为标记类型。然后所有类似标记的类型在彼此使用时都表现为它们的未标记类型,但是混合具有不同标记的对象应该是编译时错误。我想将未标记类型和标记类型之间的操作结果变为标记也是有意义的。

这类似于单位,但我想把任何东西变成多种互斥"units"。例如,TagWrapper<Person, type1> 具有 Person 的接口,但不会与 TagWrapper<Person, type2> 交互。

要为不同的框架制作不同的类型,只需将其作为模板参数即可。然后我们需要在类型上定义我们想要的任何接口。这是您可以编写的示例:

#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;
};

如果您想要问题中的确切语法,您可以将 template <typename T, typename Tag, Tag kTag> 替换为 template <typename T, Frames frame> 并进行必要的更改,或者您可以使用此类型别名:

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

有了这个,混合两种标记类型会导致编译错误,但混合标记类型和未标记类型会转换为标记类型。剩下的就是定义标记类型之间的转换函数,这很容易做到:

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

然后是这个:

auto pt4 = (tf_1_to_2 * pt1) + pt2;

变成这样:

auto pt4 = to_frame1(pt1) + pt2;