【问题标题】:Why can't a constant pointer be a constant expression?为什么常量指针不能是常量表达式?
【发布时间】:2011-11-15 13:31:39
【问题描述】:

以下程序编译:

template <const int * P>
class Test{};

extern const int var = 42; //extern needed to force external linkage

int main()
{
    Test<&var> test;
}

然而,这个没有,这对我来说是一个惊喜:

template <const int * P>
class Test{};

extern const int var = 42; //extern needed to force external linkage
extern const int * const ptr = &var; //extern needed to force external linkage
int main()
{
    Test<ptr> test; //FAIL! Expected constant expression.
}

替代示例:

int main()
{
   const int size = 42;
   int ok[*&size]; //OK

   const int * const pSize = &size;
   int fail[*pSize]; //FAIL
}

我得出的结论是,指针不能是常量表达式,无论它是否为 const 并使用常量表达式进行初始化。

问题:

  1. 我的结论是真的吗?
  2. 如果是这样,为什么指针不能是常量表达式?如果没有,为什么上面的程序不能编译?
  3. C++0x(C++11,如果你愿意的话)有什么改变吗?

感谢您的任何见解!

【问题讨论】:

  • GCC 给出以下错误:“'ptr' 不是有效的模板参数,因为 'ptr' 是一个变量,而不是变量的地址” 这似乎表明指针模板参数必须是address-of...的实际结果?
  • @Kerrek SB:看我的第二个例子 - 指针似乎不能是常量表达式......不知何故......莫名其妙
  • 不过,GCC 错误更具体。不仅仅是ptr 不是一个常量表达式(它实际上是),而是它是一个变量这一事实。不过,不知道标准中的哪些内容。
  • @Kerrek SB:我怀疑它没有明确涵盖。这是我们应该推断的事情之一 :) 无论如何,我对 why 方面更感兴趣。我的意思是,显然,指针的值是一个常量表达式,但指针本身不是。有什么障碍可以做到这一点?

标签: c++ pointers c++11 constant-expression


【解决方案1】:

这有点复杂。在 C++03 和 C++11 中,如果 var 是本地静态/类静态或命名空间范围变量,则 &amp;var 是常量表达式。这称为地址常量表达式。使用该常量表达式初始化类静态或命名空间范围指针变量保证在任何代码运行之前完成(静态初始化阶段),因为它是一个常量表达式。

但是只有从 C++11 开始,存储地址 &amp;var 的 constexpr 指针变量也可以用作地址常量表达式,并且只有从 C++11 开始,您可以取消引用地址常量表达式(实际上,您可以取消引用更多 - 甚至是本地数组元素地址,但让我们保持主题)并且如果它引用在取消引用之前初始化的常量整数变量或 constexpr 变量,您将再次获得一个常量表达式(根据类型和值类别,常量表达式的种类可能会有所不同)。因此,以下是有效的 C++11:

int const x = 42;
constexpr int const *px = &x;

// both the value of "px" and the value of "*px" are prvalue constant expressions
int array[*px];
int main() { return sizeof(array); }

如果是这样,为什么指针不能是常量表达式?如果没有,为什么上面的程序不能编译?

这是标准措辞中的一个已知限制 - 它目前只允许其他模板参数作为参数或&amp; object,用于指针类型的模板参数。即使编译器应该能够做更多的事情。

【讨论】:

  • 你是在暗示我的`const int size = 42; int ok[*&size]; //OK` 在 C++03 中实际上是病态的?
  • @Armen 是的。 GCC 接受一些格式错误的表达式,因为它会提前折叠一些它认为多余的运算符序列。
  • 尽管如此,GCC 拒绝 Test&lt;px&gt; because ‘px’ is a variable, not the address of a variable... 这是实现中的错误吗?
  • @KerrekSB 无法使用 GCC8.2 和 -std=c++17 重现:godbolt.org/z/Oyfypv
【解决方案2】:

在 C++0x 中仍然不允许。 temp.arg.nontype 要求:

非类型、非模板模板参数的模板参数应为以下之一:

  • 对于整数或枚举类型的非类型模板参数,模板参数类型的转换常量表达式 (5.19);或
  • 非类型模板参数的名称;或
  • 一个常量表达式 (5.19),它指定具有静态存储持续时间的对象的地址,并且 外部或内部链接或具有外部或内部链接的功能,包括功能模板 和函数模板 ID,但不包括非静态类成员,表示(忽略括号)为 &amp; id-expression,但如果名称指的是函数或数组,则 & 可以省略,并且应该 如果相应的模板参数是引用,则省略;或
  • 计算结果为空指针值的常量表达式 (4.10);或
  • 计算结果为空成员指针值的常量表达式 (4.11);或
  • 指向成员的指针,如 5.3.1 中所述。

原答案:

  1. 在 C++03 中,只有整数表达式可以是常量表达式。
  2. 因为标准是这样说的(自然)。
  3. 在 C++0x 中,n3290 包括在指针上使用 constexpr 的示例。因此,您现在尝试的应该是可能的,尽管您现在必须使用 constexpr 关键字而不是顶级 const。

还有一个 gcc 错误,g++ rejects the standard draft's own examples of valid constexpr usage。

【讨论】:

  • 我实际上用constexpr 尝试了所有这些,结果与我在其他 cmets 中的结果相同......
  • 1) &var 不是整数,是吗? 2) 你的意思是“我不知道” 3) @Kerrek SB 的评论呢?
  • @Kerrek:gcc 在 4.5.1 中没有完全实现 C++0x。你有更新的版本来测试吗?
  • 我正在使用 4.6.1 进行测试。我们应该问有快照版本的人,但错误消息似乎非常具体和故意,所以如果这只是错误地实现,我会感到惊讶。
  • @Armen:我不认为&amp;var 是 C++03 中的常量表达式。实际的模板参数必须属于几个类别之一,常量表达式是一个,指针是另一个。 C++0x 融合了这些概念。
【解决方案3】:

问题是因为您的 C++ 程序可以在内存中的任何位置加载,因此每次运行程序时全局var 的地址可能不同。如果你运行你的程序两次会发生什么? var 那么显然在两个不同的位置。

更糟糕的是,在您的示例中,您获取了堆栈上变量的地址!看看这个:

void myfunction( unsigned int depth) {
     const int myvar = depth;
     const int * const myptr = &myvar;
     if (depth)
         myfunction(depth-1);
}

如果 main 调用 myfunction(3),则在不同的位置创建 3 个 myvar。编译时甚至无法知道创建了多少 个 myvar,更不用说确切的位置了。

最后:将变量声明为const 意味着:“我保证”,not 是否意味着这是一个编译时间常数。看这个例子:

int main(int argc, char** argv) {
    const int cargc = argc;
    char* myargs[cargc]; //the size is constant, but not a _compile time_ constant.
}

【讨论】:

  • 我知道 const 和常量表达式的区别。您的回答与我的第二个示例中格式良好的部分相矛盾。而且它也与第一个格式良好的例子相矛盾。
  • 嗯...我不确定我是否相信。毕竟,Test&lt;&amp;var&gt; 确实有效,尽管 var 的位置不确定。
  • 指针是常量并在编译时初始化。您在指针中看到的值不是绝对值,而是与操作系统知道的绝对地址的偏移量。即使程序同时运行两次,指针也可以具有相同的值,但实际上指向两个不同的地址。
  • 指针中的值是绝对虚拟地址。通过虚拟->物理地址转换的魔力,允许程序的多个同时实例;每个进程都有自己的翻译表。
  • 对了,忘记虚拟内存了。好吧,对我来说还有另一个完全错误的答案。 @Kerrek SB:这是一个非常可靠的证据。
猜你喜欢
  • 2019-11-13
  • 2022-10-08
  • 2021-02-17
  • 1970-01-01
  • 2019-06-22
  • 1970-01-01
  • 1970-01-01
  • 2014-08-26
  • 2019-06-22
相关资源
最近更新 更多