【问题标题】:calculating factorial using template meta-programming使用模板元编程计算阶乘
【发布时间】:2011-03-06 03:46:24
【问题描述】:

我不明白这段代码(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
}
  • 这个奇怪的模板是什么? 接受&lt;int N&gt;?
  • 这是什么秒 奇怪的模板&lt;&gt;?
  • 什么是 enumerations 为?
  • 有什么优势 使用这个而不是正常的 运行时阶乘计算?
  • 你们多久使用一次?我已经使用 C++ 一段时间了,但以前从未使用过。我错过了多少 C++?

谢谢!

【问题讨论】:

标签: c++ templates metaprogramming


【解决方案1】:
  • 这个带有&lt;int N&gt; 的奇怪模板是什么?

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

  • 第二个奇怪的template &lt;&gt; 是什么?

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

  • 枚举有什么用?

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

  • 使用这种方法而不是正常的运行时阶乘计算有什么优势?

首先创建此代码的原因是为了创建一个概念证明,证明微积分可以使用元编程来完成。优点是生成的代码非常高效(调用Factorial&lt;4&gt;::value相当于在代码中简单地写“24”。

  • 你们多久使用一次?我已经使用 C++ 一段时间了,但以前从未使用过。我错过了多少 C++?

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

【讨论】:

  • 非类型模板参数不限于整数。它们可以是任何整数或枚举类型、指向对象的指针或指向函数的指针、对对象的引用或对函数的引用,或指向成员的指针。
  • @Benoît:我对模板了解不多,但第二种情况不应该是template &lt;0&gt; struct Factorial而不是template &lt;&gt; struct Factorial&lt;0&gt;吗?
  • 类型 arguments 参数不能以struct 为前缀。这样做会声明一个非类型参数:template&lt;struct A&gt; struct H { }; 格式错误,因为非类型参数属于类类型。
  • @R Samuel:你忘记了模板。有模板模板参数。
  • "调用 Factorial::value 相当于调用一个只返回 24 的函数" 实际上它的开销甚至比函数调用还要小。相当于使用了一个已经定义为24的枚举值,基本相当于直接写int value = 24;
【解决方案2】:

这个奇怪的模板是什么 &lt;int N&gt;?

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

第二个奇怪的模板是什么?

考虑模板的最佳方式是做类似于编译器所做的事情:将模板视为一个类,在其中将模板参数替换为该类型的实际值。

也就是说,对于:

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

Factorial&lt;2&gt;::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&lt;10&gt;::value 的运行时成本与 Factorial::value 相同。在编译后的代码中,它们都是常量。如果您要计算性能关键代码中的阶乘,并且您在编译时就知道它是哪个值,您应该这样做,或者离线计算它并在代码中使用预先计算的值定义一个常量。

你们多久使用一次?

你所指的“this”是指一个常数的递归计算吗?如果是这样,则不经常。

“this”是模板中常量的定义吗?很多时候:)

“this”是针对特定类型的模板的特殊化吗?非常非常非常经常。我知道 std::vector 在某些 STL 实现中具有完全独立的实现(具有 8 个元素的 std::vector&lt;bool&gt; 应该不需要超过 1 个字节来存储值)。

我使用 C++ 已经有一段时间了, 但以前从未使用过。多大一个 我错过了 C++ 的一部分吗?

不一定是很大一部分。如果你使用boost,那么你使用的代码很可能是使用这样的东西实现的。

【讨论】:

  • 乘法仍然在运行时执行。在编译时只进行了操作准备。
【解决方案3】:

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

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


是的,它是一个非类型参数。你可以有几种模板参数

  • 类型参数。
    • 类型
    • 模板(只有类,没有函数)
  • 非类型参数
    • 指针
    • 参考文献
    • 整型常量表达式

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

模板类型参数:

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&lt;&gt; 保留用于标记显式模板特化,而不是没有参数的模板:

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

【讨论】:

  • 如果您想链接到相关答案,请使用 cmets。不要只是复制/粘贴完整的答案而不自己添加任何内容。
【解决方案4】:

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

template <>
struct Factorial<0>
{
}

是递归的退出点。

首先Factorial&lt;4&gt; 已解决。对于值 4,Factorial 没有特化,因此使用第一个定义 Factorial&lt;&gt;。 然后用Factorial&lt;3&gt;::value计算它的值,重复上一步。

这将继续,直到 N==1,然后对于 N-1,特化 Factorial&lt;0&gt; 开始发挥作用,其中值设置为 1。递归在此处停止,编译器可以向上计算并计算Factorial&lt;N&gt; 的剩余值。

【讨论】:

    【解决方案5】:

    使用这个而不是正常的运行时阶乘计算有什么好处?

    它更快——理论上编译器会在编译时扩展模板集,使得Factorial&lt;n&gt;::value 只是一个单一的常量。在极少数情况下,这可能会产生很大的不同。

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

    你们多久使用一次?我已经使用 C++ 有一段时间了, 但以前从未使用过。我错过了多少 C++ 部分?

    这样的案例,很少见。模板元编程可能非常强大,但是这个示例对性能有用且足够重要的情况很少。

    但它在 C++ 中是一项有用的技术,因为它允许该语言执行许多强大的技巧,否则这些技巧是遥不可及的。我敢说,利用其他人为您完成的模板元编程更为常见 - 例如。 Boost 大量使用它,因此可以做一些非常聪明的事情。它很强大,但如果做得不好也会混淆代码。

    【讨论】:

      猜你喜欢
      • 2012-01-25
      • 2020-11-26
      • 1970-01-01
      • 2022-01-17
      • 1970-01-01
      • 2023-03-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多