元编程中的枚举值与静态常量

Enumeration values versus static constants in metaprogramming

我正在阅读 C++ 模板:完整指南,第 23 章元编程。最后,它描述了 在元编程中使用枚举值与静态常量的区别。考虑以下计算 3 的 N 次方的两种实现:

枚举实现:

// primary template to compute 3 to the Nth
template<int N>
struct Pow3 {
   enum { value = 3 * Pow3<N-1>::value };
};

// full specialization to end the recursion
template<>
struct Pow3<0> {
   enum { value = 1 };
};

静态常量实现:

// primary template to compute 3 to the Nth
template<int N>
struct Pow3 {
   static int const value = 3 * Pow3<N-1>::value;
};
// full specialization to end the recursion
template<>
struct Pow3<0> {
   static int const value = 1;
};

紧接着它说后一个版本有一个缺点。如果我们有一个函数 void foo(int const&); 并将元程序的结果传递给它:foo(Pow3<7>::value); 书上说:

A compiler must pass the address of Pow3<7>::value, and that forces the compiler to instantiate and allocate the definition for the static member. As a result, the computation is no longer limited to a pure “compile-time” effect. Enumeration values aren’t lvalues (i.e., they don’t have an address). So, when we pass them by reference, no static memory is used. It’s almost exactly as if you passed the computed value as a literal.

老实说,我完全不明白他们的解释。我认为在静态常量版本中我们在编译时评估结果,但实际引用发生在 运行 时间,因为我们需要传递地址。但是我看不出前一种情况(枚举实现)有什么不同,因为我们应该在那里进行临时物化(prvalue -> xvalue 转换),并且由于临时对象也有地址,所以我的思考过程失败了。它还说“没有使用静态内存”我也不明白,因为静态内存应该指的是在编译时发生的分配。

有人可以更详细地解释整个事情吗?

我建议这样看:
类型、类型的实例和类型可以采用的值之间存在差异。

在第一种情况下,您指定的类型(未命名的枚举)只有一个可能的值 value(即编译时常量)。
没有类型的实例化,因此不会在编译时或运行时使用内存。
每次代码引用 Pow3<N>::value 时,编译器都不会创建该类型的实例,而是直接使用常量。

在第二种情况下,您指定了一个 int 类型的变量 value,并将编译时常量分配给它。
这个变量存在,所以会占用内存。
每次代码引用 Pow3<N>::value 时,编译器都会使用所述变量。

(enum implementation) because we should have temporary materialization there (prvalue -> xvalue conversion) and since temporary objects also have the address

他们这样做,但临时 xvalue 仅在函数调用期间存在。它等同于向函数传递整数文字。因此,可寻址 object 在运行时暂时存在,具有自动作用域。

反之,

... that forces the compiler to instantiate and allocate the definition for the static member. As a result, the computation is no longer limited to a pure “compile-time” effect.

static const int 是 object,存储期限为 static。不需要临时具体化,它是一个 object,它在您的程序启动时就存在,您正在引用它。

如果您编写多个 .cpp 文件,包括 header 和 Pow3,它们都调用 foo(Pow3<3>):

  • enum 版本将在每个翻译单元中发出类似于 foo(27) 的内容,其中包含三个不相关的临时 xvalues
  • static 版本将发出一个全局 object 相当于 const int Pow3<3>::value = 27; 并且每个翻译单元将引用(即引用)相同的 object