使用模板元编程提供强大的坐标系

Using template metaprogramming to provide strongly-typed coordinate systems

本文关键字:坐标系 编程      更新时间:2023-10-16

我正在尝试定义一个3D'坐标'类,该类在'CoordinateSystem'参数上进行了强烈构成。我正在使用的当前机制的简化版本:

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 */
}

这有效,但是我对树中每个级别所涉及的样板量不满意(无argument/explicit values/copy/move构造函数,分配运算符)。我想保留具有不同系统的坐标轴的不同名称的能力。

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

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

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

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

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

,但这将变得相当丑陋,并放置了所有不愉快的模板机械,此类无辜的用户将看到它。

我不确定这种方法是哪种最佳方法。我如何避免在保持我想要的接口时定义这么多样板构造函数/分配运算符?

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

上面的代码无效C ,因为T是一种类型,但是您使用的IS好像是template。这也不是CRTP-这是适当CRTP的示例:

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>
{
    // ...
};

您可以尝试以这种方式使用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); }
    double _x, _y, _z;
    base(double x, double y, double z) : _x{x}, _y{y}, _z{z} { }
    base(double v) : _x{v}, _y{v}, _z{v} { }
};
struct coord : private base<coord>
{
public:
    using base<coord>::base;
    auto& x() { return this->_x; }
    auto& y() { return this->_y; }
    auto& z() { return this->_z; }
};
struct geocoord : private base<geocoord>
{
public:
    using base<geocoord>::base;
    auto& lat() { return this->_x; }
    auto& lon() { return this->_y; }
    auto& alt() { return this->_z; }
};
int main()
{
    coord c0{1,2,3};
    geocoord c1{1};
    c0.x() = 10;
    c1.alt() = 22;
}

wandbox上的实时示例

最终解决了这样的问题:

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;

sota-crtp,但我依靠约定来使static_cast&lt;>安全而不是模板参数和约定。我已经阐述了一些名称空间和事物,这意味着坐标和system1并不是很暴露于公众。

这种方法的优点:

  • 坐标系统类只有坐标系统专门允许(主要获得坐标)的操作
  • 所有的构造函数都生活在顶层,因此无需定义或using在下部下方
  • 在层次结构的顶部有一个类,我可以为所有坐标(或一些重要的子集)定义事物,例如算术操作
  • 相对安全。我可以static_assert,坐标已通过坐标键的某些东西,我可以将所有系统保留在自己的名称空间中,以"内部"之类的名称,因此不鼓励人们使用它们,可以使坐标系统抽象使他们可以抽象't自己实例化,如果您忘记添加"朋友"声明,它不会编译,并且只能通过适当的坐标名称访问坐标。
  • 易于理解。

缺点:

  • 没有真正的方法来确保坐标库中的static_cast是安全的。
  • 朋友声明