前言
我们知道,只要使用函数模板,(编译器)会自动地引发一个实例化过程,因此我们并不需要额外地请求模板的实例化。
用具体类型代替模板参数的过程叫做实例化(instantiation)。它产生了一个模板的实例,也就是生成了新的代码,导致了代码的膨胀。
那我们为什么不用函数参数替代非类型模板参数呢?这样不是会减少代码的膨胀?
结论
简而言之,非类型模板参数就是为了空间换取时间,是为了更高的代码优化,更快的运行速度
转载内容
英文源地址:https://stackoverflow.com/questions/38270796/why-are-non-type-template-arguments-used
中文翻译地址:https://www.it1352.com/2575955.html
问题描述
我已经阅读了很多问题和答案,但是这个问题最吸引我的眼球;它及其答案很有帮助,但我仍然觉得我没有完全理解使用非类型模板参数背后的用途和理论.它们提供了许多关于何时使用它的有用示例,但没有一个真正阐明非类型模板参数背后的理论.
我感兴趣的不是在示例中具体是什么时候,而是更广泛地了解为什么我倾向于使用非类型模板参数而不是常规函数参数.
我知道它们是在编译时确定的,并且每个新调用都会创建一个新函数,该函数具有非类型模板参数的确定值.那么,当我可以将我想要的参数输入到一个函数中并得到相同的结果——据说——只有一个编译函数时,为什么我要为同一个函数创建许多不同的实例.
从本质上讲,为什么我应该倾向于做#1,而不是#2,根据https://cplusplus.com/这一页https://cplusplus.com/doc/tutorial/functions2/的最后一部分?
#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';
}
另外,是否有任何性能优势或其中之一?是否有任何常见应用程序可以从使用非类型模板参数中受益,或者这是在特定应用程序中使用的技术性很强的参数来产生特定结果?
由于某种原因,这被标记为重复,第一段解释了我提出类似问题的原因.
推荐答案
当您希望(或需要)编译器在保持代码良好分解的同时进行某些编译时优化时,它们非常有用.我将简要列出几个示例:
分支消除
void doSomething(bool flag) {
if (flag) {
//whatever
}
}
template <bool flag>
void doSomething() {
if (flag) {
//whatever
}
}
在上面的例子中,如果你在编译时调用doSomething时总是知道flag的值,那么你就可以避免分支的开销,而无需手动创建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
}
如果编译器知道 N 是 4 或 8,它可能能够用等效的 SIMD 指令替换您的算术,或者至少比 N 是运行时参数时更智能地展开您的循环.这种事情在 GPU 编程 (CUDA) 世界中司空见惯.
模板元编程
当您使用模板生成一些非平凡的代码时,有时您必须在编译时迭代事物.您需要模板非类型参数才能做到这一点.参数包和 std::tuple 是一种常见的情况,但还有更多,这里很难举例说明.
回答您的问题,如果您有选择,何时将某些内容作为模板参数:采用动态(运行时)参数的代码总是更灵活,因此如果可以,您应该只将某些内容转换为模板参数用性能的显着提高来证明它是合理的.另一方面,如果您发现自己在一组枚举上编写了大量重复的代码,您可能应该尝试使用模板进行分解.