使用非类型模板参数而不是常规参数的原因?

时间:2011-09-13 00:54:22

标签: c++ templates

在C ++中,您可以使用非类型模板参数创建模板,如下所示:

template< int I >
void add( int& value )
{
  value += I;
}

int main( int argc, char** argv )
{
  int i = 10;
  add< 5 >( i );
  std::cout << i << std::endl;
}

将“15”打印到cout。有什么用?是否有任何理由使用非类型模板参数而不是更常规的类似参数:

void add( int& value, int amount )
{
  value += amount;
}

很抱歉,如果已经问过这个问题(我看了但找不到任何东西)。

6 个答案:

答案 0 :(得分:23)

非类型模板参数有很多应用程序;这里有几个:

您可以使用非类型参数来实现表示固定大小的数组或矩阵的泛型类型。例如,您可以在其维度上参数化Matrix类型,这样您就可以生成Matrix<4, 3>Matrix<2, 2>。如果然后正确定义这些类型的重载运算符,则可以防止意外错误添加或乘以不正确维度的矩阵,并且可以使函数明确地传达它们接受的矩阵的预期维度。这可以通过在编译时检测违规来防止发生大量的运行时错误。

您可以使用非类型参数通过模板元编程实现编译时功能评估。例如,这是一个在编译时计算阶乘的简单模板:

template <unsigned n> struct Factorial {
    enum { 
       result = n * Factorial<n - 1>::result
    };
};
template <> struct Factorial<0> {
    enum {
       result = 1
    };
};

这允许您编写类似Factorial<10>::result的代码,以便在编译时获得10!的值。这可以防止在运行时执行额外的代码。

此外,您可以使用非类型参数来实现compile-time dimensional analysis,这允许您定义千克,米,秒等类型,以便编译器可以确保您不会意外地使用千克你的意思是米等等。

希望这有帮助!

答案 1 :(得分:7)

在这种情况下你可能是对的,但有些情况下你需要在编译时知道这些信息:

但是这个怎么样?

template <std::size_t N>
std::array<int, N> get_array() { ... }

std::array需要在编译时知道它的大小(因为它是在堆栈上分配的)。

你做不到这样的事情:

std::array<int>(5);

答案 2 :(得分:5)

在那个特定的例子中,没有任何优势。但是使用这样的模板参数,你可以做很多你不能做的事情,比如有效地将变量绑定到函数(比如boost::bind),在函数或类中指定编译时数组的大小(std::array是其中的一个现成例子)等等。

例如,使用该函数,您可以编写类似

的函数
template<typename T>
void apply(T f) {
    f(somenum);
}

然后你可以传递apply一个函数:

apply(&add<23>);

这是一个非常简单的例子,但它证明了这一原则。更高级的应用程序包括将函数应用于集合中的每个值,计算编译时函数的阶乘等等。

你不能以任何其他方式做任何事情。

答案 3 :(得分:5)

嗯,这是编译时多态和运行时多态之间的典型选择。

从您问题的措辞中可以看出,您在“普通”模板参数中看不到任何异常,同时将非类型参数视为奇怪和/或冗余。实际上,同样的问题也可以应用于模板类型参数(您称之为“普通”参数)。相同的功能通常可以通过具有虚函数的多态类(运行时多态)或通过模板类型参数(编译时多态)来实现。我们也可以问为什么我们需要模板类型参数,因为几乎所有东西都可以使用多态类来实现。

如果是非类型参数,您可能希望有一天这样的事情

template <int N> void foo(char (&array)[N]) {
  ...
}

,无法使用运行时值实现。

答案 4 :(得分:1)

有很多原因,比如做模板元编程(检查Boost.MPL)。但是没有必要这么做,C ++ 11的std::tuple有一个访问者std::get<i>需要在编译时编入索引,因为结果取决于索引。

答案 5 :(得分:1)

我能想到的最常用的值参数是std::get<N>,它检索std::tuple<Args...>的第N个元素。第二最常用的是std::integral_constant及其主要衍生物std::true_typestd::false_type,它们在任何类型的特质类别中无处不在。事实上,类型特征绝对充满了值模板参数。特别是,有SFINAE技术利用签名模板<typename T, T>来检查类成员是否存在。