作为编写性能敏感的数值模拟代码的一部分,出现了一个设计问题,我花了几天时间思考并找不到令人满意的解决方案。作为我的模拟的一部分,我有一个相当大的容器类模板,其部分特化包含与模拟的该部分相关的数据成员。但是,应用于该数据的转换可能会有很大差异,并且通过许多不同的模板参数提供。这是我想到的一种简化的最小玩具示例:
enum AlgType1 {Alg1Kind1, Alg1Kind2};
enum AlgType2 {Alg2Kind1, Alg2Kind2};
enum AlgType3 {Alg3Kind1, Alg3Kind2};
template<int D=2, AlgType1 T1=Alg1Kind2, AlgType2 T2=Alg2Kind1, AlgType3 T3=Alg3Kind1>
struct Grid {};
template<AlgType1 T1, AlgType2 T2, AlgType3 T3> // etc.
struct Grid<2, T1, T2, T3> {
const int N1, N2;
const SomeData d1, d2;
Grid(const GridParams params) : N1(params.N1), N2(params.N2), d1(params.d1), d2(d2) {}
template<AlgType T1>
SomeData AlgImplementation;
};
template<AlgType1 T1, AlgType2 T2, AlgType3 T3>
template<>
SomeData Grid<2,T1,T2,T3>::AlgImplementation<Alg1Kind1>() {
return d1*N1 + d2*N2; // just do something with the data in Grid
}
不幸的是,这种方法不起作用,因为除非类模板也是完全专业化的,否则不允许使用完全显式的成员模板特化。在我的情况下,这意味着明确地拼写出我所写的所有不同算法类型的所有可能组合,因此这不是一个选项。如果我可以将传统的运行时多态性与虚函数一起使用,那么这个问题将是微不足道的,但是性能开销是我无法负担的(是的,我已经对它进行了基准测试,这很重要)。到目前为止我尝试的简单模板替代方案要么不进行扩展(在代码膨胀方面),因为会添加更多算法和更多类型的算法(如传统CRTP的情况)或存在重大问题他们自己的。
到目前为止,我发现的最不好的解决方案涉及一种基于策略的简单算法设计实现,具有实际工作的静态成员函数。但是,这会将算法实现与Grid类的主要范围分离,因此我必须通过每个变量传递数据,或者使用以下内容传递指向相关Grid实例的指针:
template<class Grid, AlgType1 T1> class Alg1Policy {};
template<class Grid> class Alg1Policy<Alg1Kind1> {
static SomeData run(Grid grid) {
return grid->d1*grid->N1 + grid->d2*grid->N2;
}
}
// and then in the partially specialized Grid class, the last two lines change to:
SomeData AlgImplementation() {
return Alg1Policy<decltype(this),T1>::run(this);
}
无论哪种方式,我都会产生相当大的语法开销,无法完全缓解,以及在后一种情况下追加一些额外的指针。因此,我不满意这个解决方案。我还考虑了一些涉及enable_if
和类似结构的其他选项,但随着算法数量的增加,这些方法的复杂性增长得太快。请注意,尽管这里有一些简单的例子,但我也有算法特化,它依赖于两个或多个具有特定值组合的不同模板参数。
这里的基本问题是我希望将我的数据保留在与通常从组件(策略,特征或其他结构)汇总的行为相同的范围内,以减少样板并避免不必要的性能成本。在缺乏对部分成员专业化的语言支持的情况下,有没有更好的方法让我想到?或许,或许,我想这完全是错误的方式?
答案 0 :(得分:1)
您可以使用SFINAE来专门化&#39;你的会员职能。
#include <type_traits>
enum AlgType1 {Alg1Kind1, Alg1Kind2};
enum AlgType2 {Alg2Kind1, Alg2Kind2};
enum AlgType3 {Alg3Kind1, Alg3Kind2};
template<int D=2, AlgType1 T1=Alg1Kind2, AlgType2 T2=Alg2Kind1, AlgType3 T3=Alg3Kind1>
struct Grid {};
template<AlgType1 T1, AlgType2 T2, AlgType3 T3> // etc.
struct Grid<2, T1, T2, T3> {
const int N1, N2;
const SomeData D1, D2;
Grid(const GridParams params) : N1(params.N1), N2(params.N2), D1(params.d1), D2(params.d2) {}
template<AlgType1 Ta>
std::enable_if_t<Ta==Alg1Kind1,SomeData> AlgImplementation()
{ return D1*N1 + D2*N2; }
template<AlgType1 Ta>
std::enable_if_t<Ta==Alg1Kind2,SomeData> AlgImplementation()
{ return D1*N1 - D2*N2; }
};
请注意,您可以采用不同的方式应用SFINAE,例如参数类型
template<AlgType1 Ta>
SomeData AlgImplementation(std::enable_if_t<Ta==Alg1Kind2>* =nullptr);
或第二个模板参数
template<AlgType1 Ta, typename E=std::enable_if_t<Ta==Alg1Kind2>>
SomeData AlgImplementation();
等
不过,你实现了什么样的算法? (我只是好奇)。