使用非类型模板参数有什么好处?

What's good of using non-type template parameters?

本文关键字:什么 参数 类型      更新时间:2023-10-16

众所周知,在c++中可以有非类型模板形参,如int:

template <class T, int size>
void f(){...}

我想知道它与将参数传递给函数的普通方式有何不同:

template <class T>
void f(int size) {...}

我认为一个区别是,对于模板,size是在编译时计算的,并在实例化模板时作为字面量替换。因此,我怀疑(如果我错了请纠正我)每个不同的size值都会导致创建新的二进制代码("。text"),这似乎是一个开销。

谁知道这是必要的和值得的?

因此,我怀疑(如果我错了请纠正我)每个不同大小的值都会导致创建新的二进制代码("。text"),这似乎是一个开销。

实际上就是这样,这是代码膨胀的一个常见来源。你需要弄清楚什么时候你想为每个N生成不同的函数,什么时候你想要一个编译器信息较少的函数(注意,这不仅仅是为了性能,也是为了正确性)。

既然Matt已经举了一个简单的例子,让我们来研究一个通过引用接受数组的函数:

template<typename T, size_t N>
size_t operateOnArray( T (&array)[N] )
{
     // Some complex logic, which could include:
    for (std::size_t i = 0; i < N; ++i) {
       // complicated stuff
    }
}

参数的类型是对数组的引用,编译器将为您验证数组是否确实具有N元素(并且它将推断数组中值的类型)。与一些类似的C风格代码相比,这在类型安全方面是一个很大的改进:

size_t operateOnArray( T *array, size_t N)
{
     // Some complex logic, which could include:
    for (std::size_t i = 0; i < N; ++i) {
       // complicated stuff
    }
}

特别是,用户可能会错误地传递错误的值:

int array[10];
operateOnArray(arrah, 20); // typo!!!

在第一种情况下,编译器将推断出大小,并保证它是正确的。

当你提到这可能会增加代码的大小时,你一针见血地说到了点子上,而且会增加很多。假设这个函数足够复杂,不能内联,再假设在你的程序中,你调用的函数大小从1到100都有。程序代码包含100个基本相同代码的实例化,唯一的区别是大小。

有一些解决方案,比如混合使用这两种方法:

size_t operateOnArray( T *array, size_t N); // Possibly private, different name...
template<typename T, size_t N>
size_t operateOnArray( T (&array)[N] ) {
   operateOnArray(array, N);
}

在这种情况下,编译器将在c风格函数中拥有复杂代码的一个副本,并将生成100个版本的模板,但这些都很简单,简单到编译器内联代码并将程序转换为具有保证类型安全的c风格方法的等效程序。

谁知道这是必要的和值得的?

是必要的当模板内的代码需要该值作为编译时常数时,例如在上面的代码中,你不能有一个函数参数是对N元素数组的引用,而N只在运行时可用。在其他情况下,如std::array<T,N>,需要静态地创建一个适当大小的数组。无论如何,所有的例子都表明:值需要在编译时已知。

如果为程序增加了类型安全性(参见上面的示例),或者如果它允许更强的优化(将函数指针/成员函数指针作为非类型参数的函子可以内联函数调用),那么是值得的。

你应该意识到一切都是有代价的,在这个例子中是二进制大小。如果模板足够小,代码很可能被内联,不用担心,但如果代码非常复杂,请考虑使用混合方法,在需要时使用模板参数,或者如果它提供了很大的优势,则使用常规参数。

除了提供更大的优化能力外,形参还可以参与模板演绎(与函数实参不同),例如,这是查找命名数组中有多少项的常见方法:

template<typename T, size_t N>
size_t lengthof( T (&array)[N] )
{
     return N;
}

用法:

#include <iostream>
int main()
{
    wchar_t foo[] = L"The quick brown fox";
    std::wcout << """ << foo << "" has " << lengthof(foo) - 1 << " characters.n";
}

编译器可能会在编译时计算长度,并将其直接替换为wcout << .......行,甚至不进行运行时函数调用。

传递size作为非类型模板参数是必要的,当f()想要分配该大小的c风格数组时(例如,作为f(){int array[size]; })。如果将size作为函数参数传递,程序将无法编译,因为在编译时不知道该大小。

编译时量纲分析。