具有编译时常量的虚函数

时间:2016-01-07 15:33:28

标签: c++ templates inheritance variadic-templates

我将首先提出我的问题并在下面添加一些更长的解释。我有以下类设计无法正常工作,因为C ++不支持虚拟模板方法。我很乐意了解实现此行为的替代方法和变通方法。

class LocalParametersBase
{
public:
  template<unsigned int target>
  virtual double get() const = 0;   //<--- not allowed by C++
};

template<unsigned int... params>
class LocalParameters : public LocalParametersBase
{
public:
  template<unsigned int target>
  double get() const;               //<--- this function should be called
};

由于以下原因,目前使用简单的函数参数而不是模板参数是无法替代的:

  1. 派生类中此方法的实现依赖于一些模板元编程(使用可变参数类模板参数)。据我所知,不可能使用函数参数(即使它们是常量整数类型)作为模板参数。
  2. 该方法仅使用编译时常量调用。性能在我的应用程序中至关重要,因此我希望从编译时的计算中受益。
  3. 需要公共基类(为简洁起见,我省略了界面的其余部分)。
  4. 非常感谢任何建议。

    更新:动机

    由于关于这种布局的动机存在很多问题,我将尝试用一个简单的例子来解释它。想象一下,你想测量三维空间中的轨迹,在我的具体例子中,这些是磁场中带电粒子(固定质量)的轨迹。您可以通过近似为2D曲面的敏感探测器测量这些轨迹。在轨道与敏感探测器的每个交叉点处,轨迹由5个参数唯一标识:

    • 两个局部坐标,用于描述轨道与探测器表面 local 坐标系中曲面的交点(这就是为什么类名是选择这种方式),
    • 指定轨迹方向的两个角度,
    • 一个参数,包含有关粒子的动量和电荷的信息。

    因此,轨迹完全由一组五个参数(以及相关表面)识别。但是,单个测量仅包括前两个参数(表面的局部2D坐标系中的交点)。这些坐标系可以是不同类型(kartesian,圆柱形,球形等)。因此,每个测量可能限制整个5个参数组中的不同参数(或者甚至可能是这些参数的非线性组合)。然而,拟合算法(例如,考虑简单的chi2最小化器)不应该取决于测量的特定类型。它只需要计算残差。看起来像是

    class LocalParametersBase
    {
    public:
       virtual double getResidual(const AtsVector& fullParameterSet) const = 0;
    };
    

    这很好用,因为每个派生类都知道如何在其局部坐标系上映射完整的5-d参数集,然后它可以计算残差。我希望这能解释为什么我需要一个共同的基类。还有其他与框架相关的原因(例如现有的I / O基础结构),您可以将其视为外部约束。
    您可能想知道上面的示例不需要我想要的模板get方法。只应将基类暴露给用户。因此,如果您有一个LocalParameterBase对象列表并且可以使用它们来拟合轨迹,那将会非常混乱。您甚至可以获得测量的本地参数的值。但是,您无法访问实际测量值的信息(这会使以前的信息无效)。

    我希望这可以解释我的问题。我感谢到目前为止收到的所有意见。

    对于我当前的项目,我正在编写一个类,其主要目的是充当固定大小的稀疏向量的包装器。不是存储整个向量(这是一些系统状态的表示),而是我的类具有减小大小的向量作为成员变量(=对应于总参数空间的子域)。我希望下面的插图让您了解我想要描述的内容:

    VectorType(5) allParameters = {0.5, 2.1, -3.7, 4, 15/9};   //< full parameter space
    VectorType(2) subSpace      = {2.1, 4};                    //< sub domain only storing parameters with index 1 and 3
    

    为了能够连接到原始载体,我需要&#34;存储&#34;复制到我&#34;缩短&#34;的索引向量。这是使用非类型可变参数模板参数实现的。我还需要能够使用特定索引查询参数的值。如果此参数未存储在&#34;缩短的&#34;中,则应该产生编译时错误。向量。我的简化代码如下:

    template<unsigned int... index>
    class LocalParameters
    {
    public:
      template<unsigned int target>
      double get() const;
    
    private:
      AtsVectorX m_vValues;
    };
    
    LocalParameters<0,1,4> loc;
    //< ... do some initialisation ...
    loc.get<1>();  //< query value of parameter at index 1
    loc.get<2>();  //<-- this should yield a compile time error as the parameter at index 2 is not stored in this local vector class
    

    我设法使用一些简单的模板编程来实现这种行为。但是我的代码的其他部分需要对待这些&#34;缩短&#34;矢量均匀地通过一个界面。我仍然希望能够通过接口LocalParametersBase访问是否存储具有特定索引的参数的信息(如果不是我想获得编译时错误),如果不是,我想访问此参数的值。在代码中,这应该类似于

    LocalParametersBase* pLoc = new LocalParameters<0,1,3>();
    pLoc->get<1>();
    

1 个答案:

答案 0 :(得分:1)

建议

如果没有关于你正在做什么的更多信息,我只是在做出有关推动你采用这种方法的原因的有根据的猜测。

依赖于虚拟接口的代码的常见性能问题是框架提供了以非常高的频率分派给虚拟方法的通用功能。这似乎是您面临的问题。您有对稀疏向量执行计算的代码,并且您希望为其提供表示您恰好创建的每个稀疏向量的通用接口。

void compute (LocalParametersBase *lp) {
    // code that makes lots of calls to lp->get<4>()
}

然而,另一种方法是通过使用模板参数来表示被操纵的派生对象类型,从而使计算通用。

template <typename SPARSE>
void perform_compute (SPARSE *lp) {
    // code that makes lots of calls to lp->get<4>()
}

get<>的模板版本中的每个compute调用都是针对派生对象的。这使得计算的发生速度与您编写代码以直接操作LocalParameters<0,1,4>一样快,而不是按get<>调用执行动态调度。

如果在执行计算时必须允许框架控件,并且因此在基类上执行计算,则基类版本可以调度到虚方法。

class ComputeBase {
public:
    virtual void perform_compute () = 0;
};

void compute (LocalParametersBase *lp) {
    auto c = dynamic_cast<ComputeBase *>(lp);
    c->perform_compute();
}

通过使用CRTP,您可以创建一个辅助类,它将派生类型作为模板参数,并通过传入派生类来实现此虚方法。因此,计算只需要一次动态调度,其余的计算都是在实际的稀疏矢量本身上进行的。

template <typename Derived>
class CrtpCompute : public ComputeBase {
    void perform_compute () {
        auto d = static_cast<Derived *>(this);
        perform_compute(d);
    }
};

现在你的稀疏向量来自这个辅助类。

template <unsigned int... params>
class LocalParameters
    : public LocalParametersBase,
      public CrtpCompute<LocalParameters<params...>> {
public:
    template <unsigned int target> double get() const;
};

使您的界面按指定方式工作

计算结果后,您希望将生成的稀疏矢量放入容器中以便以后检索。但是,这应该不再是性能敏感操作,因此您可以使用下面描述的方法来实现这一点。

  

基本模板方法
→基本模板类虚拟方法
→派生模板方法

如果您希望使用多态,则将基类中的模板方法调用委托给虚函数。由于它是模板方法,因此虚函数必须来自模板类。您可以使用动态强制转换来获取相应的模板类实例。

template <unsigned int target>
class Target {
public:
    virtual double get() const = 0;
};

class LocalParametersBase {
public:
    virtual ~LocalParametersBase () = default;
    template <unsigned int target> double get() const {
        auto d = dynamic_cast<const Target<target> *>(this);  // XXX nullptr
        return d->get();
    }
};

要为每个Target自动执行虚拟方法,您可以再次使用CRTP,将派生类型传递给帮助程序。帮助器转换为派生类型以调用相应的模板方法。

template <typename, unsigned int...> class CrtpTarget;

template <typename Derived, unsigned int target>
class CrtpTarget<Derived, target> : public Target<target> {
    double get() const {
        auto d = static_cast<const Derived *>(this);
        return d->template get<target>();
    }
};

template <typename Derived, unsigned int target, unsigned int... params>
class CrtpTarget<Derived, target, params...>
    : public CrtpTarget<Derived, target>,
      public CrtpTarget<Derived, params...> {
};

现在,你从派生类中适当地继承了。

template <unsigned int... params>
class LocalParameters
    : public LocalParametersBase,
      public CrtpCompute<LocalParameters<params...>>,
      public CrtpTarget<LocalParameters<params...>, params...> {
public:
    template <unsigned int target> double get() const;
};