具有编译时常量的虚函数

virtual functions with compile time constants

我会先提出我的问题,然后在下面添加一些更长的解释。我有以下 class 设计,该设计无法正常工作,因为 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. 此方法在派生 class 中的实现依赖于一些模板元编程(使用可变参数 class 模板参数)。据我所知,不可能使用函数参数(即使它们是常数整数类型)作为模板参数。
  2. 该方法将仅使用编译时常量调用。性能在我的应用程序中至关重要,因此我想在编译时从计算中获益。
  3. 需要通用基础 class(为简洁起见,我省略了界面的其余部分)。

非常感谢任何建议。


更新:动机

由于对这种布局的动机有很多疑问,我将尝试用一个简单的例子来解释它。假设您想测量三维 space 中的轨迹,在我的具体示例中,这些是磁场中带电粒子(固定质量)的轨迹。您通过近似为 2D 表面的灵敏探测器 测量 这些轨迹。在轨迹与灵敏检测器的每个交叉点,轨迹由 5 个参数唯一标识:

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

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

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

我希望这可以阐明我的问题。我感谢到目前为止收到的所有评论。


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

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

为了能够连接到原始向量,我需要 "store" 复制到我的 "shortened" 向量的索引。这是使用非类型可变模板参数实现的。我还需要能够查询具有特定索引的参数值。如果此参数未存储在 "shortened" 向量中,这将产生编译时错误。我对此的简化代码如下:

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

我设法使用一些简单的模板编程来实现此行为。但是我的代码的其他部分需要通过一个接口统一处理这些 "shortened" 向量。我仍然希望能够通过接口 LocalParametersBase 访问是否存储了具有特定索引的参数的信息(如果没有,我想得到一个编译时错误),如果是,我想访问这个参数的值。在代码中,这应该类似于

LocalParametersBase* pLoc = new LocalParameters<0,1,3>();
pLoc->get<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>()
}

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

如果在执行计算时必须允许框架控制,因此计算是在基础 class 上执行的,基础 class 版本可以分派到虚拟方法。

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

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

通过使用CRTP,您可以创建一个助手class,它以派生类型作为模板参数,它通过传入派生类型来实现这个虚方法。因此,计算仅花费一次动态调度,其余计算在实际稀疏向量本身上执行。

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

现在你的稀疏向量派生自这个助手 class。

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

让您的界面按照您指定的方式工作

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

Base template method
→ Base template class virtual method
→ Derived template method

如果您希望使用多态性,则将基 class 中的模板方法调用委托给虚函数。既然是模板方法,虚函数就得来自模板class。您可以使用动态转换来获取相应的模板 class 实例。

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

现在,您从派生的 class.

中适当地继承了
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;
};