使用模板元编程提供强类型坐标系

时间:2017-09-21 09:02:16

标签: c++ templates

我正在尝试定义一个在“CoordinateSystem”参数上强类型的3d“坐标”类。我正在使用的当前机制的简化版本:

class CoordinateBase {
    protected:
        double a, b, c;

        /* millions of constructors */
}

class System1 : protected CoordinateBase {
    public:
        double& X() {return a;}
        double& Y() {return b;}
        double& Z() {return c;}

    protected:
        /* millions of constructors */
}


class System2 : protected CoordinateBase {
    public:
        double& Lat() {return a;}
        double& Lon() {return b;}
        double& Alt() {return c;}

    protected:
        /* millions of constructors */
}

template<typename T>
class Coordinate : public T {
    public:
        /* millions of constructors */
}

这样可行,但我对树中每个级别涉及的样板量(无参数/显式值/复制/移动构造函数,赋值运算符)不满意。我想保留为不同系统的坐标轴设置不同名称的能力。

使用类似CRTP的东西似乎很有帮助;然后我可以让成员将实际坐标值存储在Coordinate类中,并使用静态多态从System1和System2获取它们。不幸的是,我不能这样做:

template<typename T>
class Coordinate : public T<Coordinate<T>>

或者,至少,编译器不会让,可能是因为我缺少一些语法。

我考虑的另一种可能性是将所有启用/禁用逻辑放在Coordinate中:

template<typename T>
class Coordinate {
    public:
        std::enable_if<std::is_same<T, System1>::value, double&> X() {return a;}

    private:
        double a;
}

但是这会变得相当丑陋并且把所有令人不快的模板机器放在这个类的无辜用户会看到它。

我不确定这类事情的最佳方法是什么。如何在保持我喜欢的界面的同时避免定义如此多的样板构造函数/赋值运算符?

2 个答案:

答案 0 :(得分:3)

SQLiteOpenHelper

上面的代码不是有效的C ++,因为template<typename T> class Coordinate : public T<Coordinate<T>> 是一种类型,但您正在使用它就好像它是T一样。这也不是CRTP - 这是正确CRTP的一个例子:

template

您可以尝试以这种方式使用CTRP:

template <typename T>
struct base
{
    auto& as_derived()             { return static_cast<T&>(*this); }
    const auto& as_derived() const { return static_cast<const T&>(*this); }
};

struct derived : base<derived>
{
    // ...
};

live example on wandbox

答案 1 :(得分:0)

结束解决这个问题:

template<typename T>
class CoordinateBase {
    protected:
        Coordinate<T>* upcast() {return static_cast<Coordinate<T>*>(this);}
        const Coordinate<T>* upcast() const {return static_cast<const Coordinate<T>*>(this);}
};

class System1 : private CoordinateBase<System1> {
    double& X() {return upcast()->c1;}
    // other coordinates etc.
};

template<typename T>
class Coordinate : public T {
    public:
        /* Constructors */

    private:
        double c1;
        double c2;
        double c3;

    friend class System1;
};

typedef Coordinate<System1> System1Coordinate;

所以sorta-CRTP,但我依靠惯例来制作static_cast&lt;&gt;安全而不是模板参数和约定。我已经省略了一些名称空间和意味着CoordinateBase和System1不会对公众公开的事情。

这种方法的优点:

  • 坐标系类只具有坐标系特别允许的操作(主要是获取坐标)
  • 所有构造函数都位于顶层,因此无需在低层中定义或using它们
  • 在层次结构的顶部有一个类,我可以为所有坐标(或一些重要的子集)定义事物,比如算术运算
  • 比较安全。我可以static_assert已经传递了一个从CoordinateBase派生的Coordinate,我可以将所有系统保存在他们自己的命名空间中,其名称类似于'Internal',因此不鼓励人们使用它们,可以使坐标系统成为抽象的,这样他们就可以不能自己实例化,如果你忘记添加'friend'声明它将无法编译,你只能通过适当的坐标名访问坐标。
  • 易于理解。

缺点:

  • 没有真正的方法可以确保CoordinateBase中的static_cast是安全的,除了约定。
  • 朋友声明