我已经阅读了很多问题和答案,但this question引起了我的注意;它及其答案是有帮助的,但我仍然觉得我不完全理解使用非类型模板参数的用法和理论。它们提供了许多有用的例子,说明它何时被使用,但没有一个真正阐明非类型模板参数背后的理论。
我有兴趣在示例中不具体了解,但更广泛地说,为什么我倾向于使用非类型模板参数而不仅仅是常规函数参数。
我知道它们是在编译时确定的,并且每个新调用都会创建一个具有非类型模板参数的确定值的新函数。那么,为什么我只想在函数中输入我想要的参数并且具有相同的结果(假设只有一个编译函数)时,我想要创建相同函数的许多不同实例。
从本质上讲,根据this page的cplusplus.com的最后一部分,为什么我倾向于做#1而不是#2?
#1:
template <class T, int N>
T fixed_multiply (T val)
{
return val * N;
}
int main() {
std::cout << fixed_multiply<int,2>(10) << '\n';
std::cout << fixed_multiply<int,3>(10) << '\n';
}
#2:
template <class T>
T fixed_multiply (T val, int N)
{
return val * N;
}
int main() {
std::cout << fixed_multiply<int>(10, 2) << '\n';
std::cout << fixed_multiply<int>(10, 3) << '\n';
}
此外,是否有任何性能优势或其中任何一种?是否有任何常见的应用程序可以从使用非类型模板参数中受益,或者这是在特定应用程序中使用的高度技术性参数,以产生特定结果?
编辑:由于某种原因,这被标记为重复,第一段解释了我提出类似问题的原因。
答案 0 :(得分:7)
当您希望(或需要)编译器在保持代码良好因素的同时进行某些编译时优化时,它们非常有用。我将简要列举一些例子:
删除分支
flag
}
在上面的示例中,如果您在调用doSomething
时在编译时始终知道doSomethingTrue
的值,那么您可以避免分支的成本,而无需手动创建doSomethingFalse
和void someFunction(size_t size, float* inout) {
std::vector<float> tmp(size);
//do something with inout and tmp and then store result in inout
}
template <size_t N>
void someFunction(float *inout) {
float tmp[N]; //or std::array if you like
//do something with inout and tmp and store result in inout
}
函数可能需要您重复大量代码。例如,当您想要在网络代码中的发送和接收方之间对代码进行分解时,这个技巧很有用,而且您的代码位于性能关键堆栈的深处。
避免动态内存管理
for (size_t i = 0; i < N; ++i) {
//some arithmetic
}
在上面的示例中,如果第二个版本卡在一个关键循环中,它将会表现得更好。
允许特殊优化
考虑循环:
std::tuple
如果编译器知道N是4或8,它可能能够用等效的SIMD指令替换你的算法,或者至少比N是运行时参数更智能地展开你的循环。这种事情在GPU编程(CUDA)世界中很常见。
模板元编程
当您使用模板执行一些非平凡的代码生成时,有时您必须在编译时迭代。您需要模板非类型参数才能执行此操作。参数包和ALTER TABLE contacts
ADD CONSTRAINT contact_unique_name_phone UNIQUE (Name,Phone)
是一种常见的情况,但还有更多,很难在这里举例说明。
如果你有选择的话,回答你何时做出模板参数的问题:采用动态(运行时)参数的代码总是更灵活,所以你应该只转换一些东西作为模板参数你可以通过显着的性能提升来证明这一点。另一方面,如果您发现自己在一组枚举中编写了大量重复的代码,那么您应该尝试使用模板进行分析。