我将首先提出问题,并在下面添加一些更长的解释。我有以下无法使用的类设计,因为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
};
由于以下原因,目前无法使用简单的函数参数代替模板参数:
任何建议,高度赞赏。
更新:动机
由于存在许多有关这种布局动机的问题,因此我将尝试通过一个简单的示例对其进行解释。假设您要测量三维空间中的轨迹,在我的特定示例中,这些是磁场中带电粒子(质量固定)的轨迹。您可以通过敏感的检测器(近似于2D曲面)来测量这些轨迹。在具有敏感检测器的轨道的每个交叉点,该轨迹由5个参数唯一标识:
因此,轨迹由一组五个参数(以及相关的表面)完全识别。但是,单个测量仅包含前两个参数(曲面的局部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/