使用模板元编程计算阶乘

时间:2010-06-21 04:27:00

标签: c++ templates metaprogramming

我不明白这段代码(from Wikipedia)是如何工作的:

template <int N>
struct Factorial 
{
    enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};

// Factorial<4>::value == 24
// Factorial<0>::value == 1
void foo()
{
    int x = Factorial<4>::value; // == 24
    int y = Factorial<0>::value; // == 1
}
  • 这是什么奇怪的模板 需要<int N>
  • 这是第二个 奇怪的模板<>
  • 是什么? enum erations for?
  • 有什么好处 使用这个而不是正常 运行时因子计算?
  • 你们多久使用一次这个?我一直在使用C ++,但之前从未使用过。我错过了C ++的大部分内容?

谢谢!

5 个答案:

答案 0 :(得分:23)

  
      
  • 这个奇怪的模板需要<int N>
  •   

在C ++中,模板参数可以是类型(前缀为classtypename)或整数(前缀为intunsigned int)。我们在第二种情况下。

  
      
  • 这第二个奇怪的是什么template <>
  •   

template<> struct Factorial<0>是Factorial类模板的完全特化,这意味着0被认为是一个特殊的值,它对应于它自己的Factorial版本。

  
      
  • 什么是枚举?
  •   

枚举是在元编程C ++中计算值的方法

  
      
  • 使用此而不是正常的运行时因子计算有什么好处?
  •   

首先创建此代码的原因是创建一个概念证明,可以使用元编程来完成微积分。优点是生成的代码非常有效(调用Factorial<4>::value等同于在代码中编写“24”。

  
      
  • 你们多久使用一次这个?我一直在使用C ++,但之前从未使用过。我错过了C ++的大部分内容?
  •   

使用这种方法很少能实现这种功能,但现在越来越多地使用元编程。请参阅Boost meta-programming library以了解可以执行的操作。

答案 1 :(得分:6)

  

这个奇怪的模板是什么   <int N>

可以对类/类型,值和指针进行模板声明。您通常不会看到此表单,因为定义模板很少在模板参数中使用常量。

  

第二个奇怪的模板&lt;&gt;是什么?

考虑模板的最佳方法是执行与编译器类似的操作:将模板视为一个类,将模板参数替换为该类型的实际值。

即:对于:

template <int N>
struct Factorial 
{
    enum { value = N * Factorial<N - 1>::value };
};

Factorial<2>::value相当于:

// generated by the compiler when Factorial<2>::value is encountered in code:
struct Factorial2 { enum { value = 2 * Factorial1::value }; };
struct Factorial1 { enum { value = 1 * Factorial0::value }; };
// not generated by compiler, as it was explicitly defined in the code you ask about:
template <> struct Factorial<0> { enum { value = 1 }; }; // defines Factorial<0>
  

什么是枚举?

它们在编译时使用const value定义枚举,允许您编写客户端代码,如示例中所示。

  

使用它有什么好处   而不是正常的运行时因子   计算

优点是效率。由于在编译时扩展了值计算,因此Factorial<10>::value的运行时成本与Factorial&lt; 1&gt; :: value相同。在编译的代码中,它们都是常量。如果要在性能关键代码中计算阶乘,并且在编译时知道它是哪个值,则应该执行此操作,或者离线计算并在代码中使用预先计算的值定义常量。

  

你们多久使用一次这个?

“this”是指你对常量的递归计算吗?如果是这样,不经常。

“this”是模板中常量的定义吗?经常:)

“this”是特定类型模板的特殊化吗?非常非常非常。我知道std :: vector在一些STL实现中有一个完全独立的实现(一个std::vector<bool>,其中8个元素不需要超过1个字节来存储值。)

  

我已经使用C ++一段时间了,   但从来没有用过这个。多大了   我错过了C ++的一部分?

不一定很重要。如果你使用boost,你使用的代码很可能是用这样的东西实现的。

答案 2 :(得分:4)

我发现this answer Johannes Schaub - litb对另一个问题很有帮助。

[我在这里复制粘贴答案,假设约翰内斯对它没问题。如果他不喜欢,我会删除粘贴]


是的,它是一个非类型参数。您可以使用多种模板参数

  • 类型参数。
    • 类型
    • 模板(仅限课程,无功能)
  • 非类型参数
    • 指针
    • 参考
    • 积分常量表达式

你拥有的是最后一种。它是一个编译时常量(所谓的常量表达式),是整数或枚举类型。在标准中查找之后,我不得不将类模板移动到类型部分 - 即使模板不是类型。但它们被称为类型参数,目的是为了描述这些类型。您可以拥有指针(以及成员指针)和对具有外部链接的对象/函数的引用(可以链接到其他目标文件并且其地址在整个程序中是唯一的)。例子:

模板类型参数:

template<typename T>
struct Container {
    T t;
};

// pass type "long" as argument.
Container<long> test;

模板整数参数:

template<unsigned int S>
struct Vector {
    unsigned char bytes[S];
};

// pass 3 as argument.
Vector<3> test;

模板指针参数(将指针传递给函数)

template<void (*F)()>
struct FunctionWrapper {
    static void call_it() { F(); }
};

// pass address of function do_it as argument.
void do_it() { }
FunctionWrapper<&do_it> test;

模板引用参数(传递整数)

template<int &A>
struct SillyExample {
    static void do_it() { A = 10; }
};

// pass flag as argument
int flag;
SillyExample<flag> test;

模板模板参数。

template<template<typename T> class AllocatePolicy>
struct Pool {
    void allocate(size_t n) {
        int *p = AllocatePolicy<int>::allocate(n);
    }
};

// pass the template "allocator" as argument. 
template<typename T>
struct allocator { static T * allocate(size_t n) { return 0; } };
Pool<allocator> test;

无法使用任何参数的模板。但是没有任何显式参数的模板是可能的 - 它有默认参数:

template<unsigned int SIZE = 3>
struct Vector {
    unsigned char buffer[SIZE];
};

Vector<> test;

从语法上讲,template<>保留用于标记显式模板特化,而不是没有参数的模板:

template<>
struct Vector<3> {
    // alternative definition for SIZE == 3
};

答案 3 :(得分:3)

  

使用此而不是正常的运行时因子计算有什么好处?

速度更快 - 理论上编译器会在编译时扩展模板集,使Factorial<n>::value只是一个常量。在一些极少数情况下,这可能会产生很大的不同。

它的缺点是它必须在编译时计算,所以如果在运行时确定n,则不能使用该方法。

  

你们多久使用一次这个?我一直在使用C ++一段时间了,   但从来没有用过这个。我错过了C ++的大部分内容?

这样的情况,根本不常见。模板元编程可能非常强大,但此示例有用且对性能而言非常重要的案例数量很少。

但它在C ++中是一种有用的技术,因为它允许语言执行许多强大的技巧,否则这些技巧将无法实现。我敢说,利用其他人为你做过的模板元编程更为常见 - 例如。 Boost使用它非常重,因此可以做一些非常聪明的事情。它功能强大,但如果做得不好也会混淆代码。

答案 4 :(得分:2)

它是由编译器执行的递归,其中

template <>
struct Factorial<0>
{
}

是递归的退出点。

首先Factorial<4>已解决。 {4}没有Factorial的特化,因此使用了第一个定义Factorial<>。 然后使用Factorial<3>::value计算其值,其中重复上一步。

这将继续,直到N == 1,然后对于N-1,专门化Factorial<0>开始起作用,其中值设置为1.递归在此处停止,编译器可以向上并且计算Factorial<N>的剩余值。