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

时间:2017-07-28 20:15:02

标签: c++ linear-algebra physics template-meta-programming

我处理大量坐标数据,想想预定义的库类型

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;

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

这类似于单位,但我想将任何东西变成多种互斥的单位&#34;。因此,TagWrapper<Person, type1>的界面为Person,但不会与TagWrapper<Person, type2>进行互动。

1 个答案:

答案 0 :(得分:1)

要为不同的帧创建单独的类型,只需将其作为模板参数。然后我们需要在类型上定义我们想要的任何接口。以下是您可以编写的示例:

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