c++ - 具有编译时间常数的虚函数

标签 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
};

由于以下原因,目前无法使用简单的函数参数代替模板参数:
  • 在派生类中此方法的实现依赖于某些模板元编程(使用可变参数类模板参数)。据我所知,不可能使用函数参数(即使它们是常量整数类型)作为模板参数。
  • 将仅使用编译时常量调用该方法。性能对我的应用程序至关重要,因此我希望在编译时从计算中受益。
  • 需要通用的基类(为简洁起见,我省略了其余的接口(interface))。

  • 任何建议,高度赞赏。

    更新:动机

    由于存在许多有关这种布局动机的问题,因此我将尝试通过一个简单的示例对其进行解释。假设您要测量三维空间中的轨迹,在我的特定示例中,这些是磁场中带电粒子(质量固定)的轨迹。您可以通过敏感的检测器(近似于2D曲面)来测量这些轨迹。在具有敏感检测器的轨道的每个交叉点,该轨迹由5个参数唯一标识:
  • 在检测器表面的局部坐标系中描述轨迹与表面的交点的两个局部坐标(这就是为什么以这种方式选择类名的原因),
  • 指定轨迹方向的两个 Angular
  • 一个参数,包含有关粒子的动量和电荷的信息。

  • 因此,轨迹由一组五个参数(以及相关的表面)完全识别。但是,单个测量仅包含前两个参数(曲面的局部2D坐标系中的交点)。这些坐标系可以具有不同的类型(笛卡尔,圆柱,球面等)。因此,每次测量都可能约束5个参数的全部集合中的不同参数(甚至可能是这些参数的非线性组合)。但是,拟合算法(例如,简单的chi2最小化器)应该不依赖于特定的测量类型。它只需要计算残差。看起来像
    class LocalParametersBase
    {
    public:
       virtual double getResidual(const AtsVector& fullParameterSet) const = 0;
    };
    

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

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

    对于我当前的项目,我正在编写一个类,其主要目的是充当固定大小的稀疏 vector 的包装。我的类不是存储整个 vector (表示某些系统状态),而是将大小减小的 vector 作为成员变量(=对应于总参数空间的子域)。我希望下面的插图使您对我要描述的内容有所了解:
    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
    

    为了能够与原始 vector 建立连接,我需要“存储”复制到“缩短” vector 的索引。这可以通过使用非类型可变参数模板参数来实现。我还需要能够使用某个索引来查询参数的值。如果此参数未存储在“缩短的” vector 中,则应该产生编译时错误。我的简化代码如下:
    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
    

    我设法使用一些简单的模板编程来实现此行为。但是我代码的其他部分需要通过一个接口(interface)统一对待这些“缩短的” vector 。我仍然希望能够通过LocalParametersBase接口(interface)访问是否存储了具有特定索引的参数的信息(如果不是,我想获取编译时错误),如果是,我想访问此参数的值。在代码中,这看起来类似于
    LocalParametersBase* pLoc = new LocalParameters<0,1,3>();
    pLoc->get<1>();
    

    最佳答案

    一条建议

    在没有更多关于您正在做的事情的信息的情况下,我只能对导致您采用这种方法的原因做出有根据的猜测。

    依赖于虚拟接口(interface)的代码的一个常见性能问题是该框架提供了以很高的频率分派(dispatch)给虚拟方法的通用功能。这似乎是您面临的问题。您具有在稀疏 vector 上执行计算的代码,并且想要为其提供一个通用接口(interface),该接口(interface)代表您碰巧创建的每个稀疏 vector 。

    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<>调用执行动态调度。

    如果在执行计算时必须允许框架控制,并且因此在基类上执行计算,则基类版本可以分派(dispatch)给虚拟方法。
    class ComputeBase {
    public:
        virtual void perform_compute () = 0;
    };
    
    void compute (LocalParametersBase *lp) {
        auto c = dynamic_cast<ComputeBase *>(lp);
        c->perform_compute();
    }
    

    通过使用CRTP,可以创建一个将派生类型作为模板参数的帮助程序类,并通过传入派生来实现此虚拟方法。因此,该计算仅花费一次动态调度,而其余的计算则针对实际的稀疏 vector 本身进行。
    template <typename Derived>
    class CrtpCompute : public ComputeBase {
        void perform_compute () {
            auto d = static_cast<Derived *>(this);
            perform_compute(d);
        }
    };
    

    现在,您的稀疏 vector 就是从此帮助器类派生的。
    template <unsigned int... params>
    class LocalParameters
        : public LocalParametersBase,
          public CrtpCompute<LocalParameters<params...>> {
    public:
        template <unsigned int target> double get() const;
    };
    

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

    计算完结果后,您想要将所得的稀疏 vector 放入容器中,以供以后检索。但是,这不再是对性能敏感的操作,因此您可以使用下面描述的方法来实现。

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



    如果希望使用多态性,则将基类中的模板方法调用委托(delegate)给虚拟函数。由于它是模板方法,因此虚函数必须来自模板类。您可以使用动态强制转换来获取相应的模板类实例。
    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;
    };
    

    关于c++ - 具有编译时间常数的虚函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34659051/

    相关文章:

    java - 代码返回节点而不是子类的值

    c# - F# 类将接口(interface)实现为私有(private)成员,为什么?

    javascript - 改进javascript原型(prototype)继承

    c++ - 尝试在 C++ 中创建新对象时出现总线错误

    c++ - 条码文件读取C++

    c++ - STA(单线程单元)COM 对象 - 生成工作线程?

    c++ - 高级 C++ 元编程中的代码无法编译

    c++ - 使用 unique_ptr 的常量字符指针

    c++ - 引用基类模板成员变量的简单方法

    c++ - 模板类相互使用会产生歧义错误