在C ++ 11中将局部变量的地址作为常量表达式吗?

时间:2013-08-21 08:24:55

标签: c++ c++11 language-lawyer constexpr pointer-arithmetic

以下C ++ 11程序:

int x = 42;

void f()
{
        int y = 43;

        static_assert(&x < &y, "foo");
}

int main()
{
        f();
}

不能使用gcc 4.7进行编译,因为它抱怨:

error: ‘&y’ is not a constant expression

这符合我的直觉。 y的地址可能会随着f的每次调用而发生变化,因此在翻译过程中无法进行计算。

然而,5.19 [expr.const]中没有一个要点可以阻止它成为一个常量表达式。

我看到的唯一两个竞争者是:

  

左值到右值的转换...

但除非我弄错了(?),否则程序中没有左值到右值的转换。

  

id-expression引用变量[snip],除非:

     
      
  • 使用常量表达式初始化
  •   

y是 - 用常量表达式43初始化。

这是标准中的错误,还是我错过了什么?

更新

这让人感到困惑,但我认为我处于最重要的位置,所以让我展示一个可以展示正在发生的事情的例子:

int x = 42;

void f()
{
        int y = 43;

        // address constant expressions:    
        constexpr int* px = &x; // OK
        constexpr int* py = &y; // ERROR: pointer context for local variable

        // boolean constant expressions:
        constexpr bool bx = &x; // OK
        constexpr bool by = &y; // OK

        // comparison constant expressions:
        constexpr bool eq = (&x == &y); // OK
        constexpr bool lt = (&x < &y); // ERROR: undefined behaviour disqualifies 
                                                 a constant expression
}

int main()
{
        f();
}

首先区分核心常数表达式(5.19p2)和常量表达式(5.19p4)。常量表达式的特定子表达式只需要是核心常量表达式,而不是常量表达式。也就是说,作为常量表达式是完整表达式的属性,而不是子表达式。它还需要查看使用完整表达式的上下文。

因此,事实证明gcc错误具有误导性。首先,&y在某些情况下可能是一个常量表达式。其次,&x < &y不是常量表达式的原因是因为不相关指针的比较,而不是子表达式&y的比较。

6 个答案:

答案 0 :(得分:10)

让我们尝试使用n3485确定 static_assert-declaration 中表达式必须逐步完成的要求。

[dcl.dcl] / 1

  

static_assert声明:
      static_assert ( 常数表达式 , 字串文本 ) ;

[dcl.dcl / 4

  

static_assert-declaration 中,常量表达式应该是一个常量表达式,可以在上下文中转换为bool

[expr.const / 4

  

文字常量表达式引用常量表达式地址常量表达式统称为常量表达式


那么&x < &y是什么类型的常量表达式?它是 地址常量表达式

[expr.const / 4

  

地址常量表达式是类型std::nullptr_t或指针类型[...]的prvalue核心常量表达式(在上下文要求的转换之后)。

根据[expr.rel] / 1,&x < &y的类型为bool

它也不是引用常量表达式,因此它必须是文字常量表达式(如果有)

  

文字常量表达式是文字类型[...]

的prvalue核心常量表达式

因此,&x < &y必须满足核心常量表达式的要求。


评论中的pointed out by TemplateRexhvd,在这种特殊情况下,&x < &y无法满足核心常量表达式的要求:

[expr.const] / 2

  

[核心常量表达式不得包含]关系或相等运算符,其中结果未指定;

[expr.rel] / 2

  

如果同一类型的两个指针pq指向不是同一对象的成员或同一数组的元素或不同函数的不同对象,或者只指向其中一个为空,p<qp>qp<=qp>=q的结果未指定。

然而,对于像

这样的例子
int arr[2] = {1, 2};
static_assert(&a[0] < &a[1], "");

表达式a < a+1也符合此要求。

答案 1 :(得分:7)

是的,你错过了这样一个事实:虽然y本身是用常量表达式初始化的,但&y相同。

y的地址可能会有很大差异,具体取决于您的调用堆栈历史记录。

C++11 5.19 Constant expressions的第3段陈述了运算符地址可被视为常量表达式的条件(允许第2段中详述的核心常量表达式变为“真实”常量表达式):

  

...地址常量表达式是指针类型的prvalue核心常量表达式,其值为具有静态存储持续时间的对象的地址函数的地址,< / strong>或空指针值或类型为std :: nullptr_t的 prvalue核心常量表达式。统称为文字常量表达式,引用常量表达式和地址常量表达式称为常量表达式。

由于&y不是那些东西,因此它不被视为常量表达式。

答案 2 :(得分:4)

取一些东西的地址不是罪魁祸首,而是使用operator<对不相关的物体进行指针比较。

指针上的关系运算符仅指定指向同一类或数组内对象的指针(5.9关系运算符[expr.rel],第3点和第4点。)未指定指向不相关对象的指针的关系比较

比较相等的地址而非订购的地址确实有效:

int x = 42;

void f()
{
        int y = 43;

        static_assert(&x != &y, "foo");
                         ^^ <--- "<" on unrelated objects is unspecified
}

int main()
{
        f();
}

Live example

只是为了表明这与const表达式本身无关,

void f()
{
        int y[2] = { 42, 43 };

        static_assert(&y[0] < &y[1], "foo");
                            ^ <--- "<" on objects within an array is specified
}

int main()
{
        f();
}

另一个live example

答案 3 :(得分:1)

抱歉,我同意之前的回答可能是对这些项目的错误阅读。相反,实际相关条款是5.19 [expr.const]第3段,其中包括(突出显示):

  

文字常量表达式是文字类型的prvalue核心常量表达式,但不是指针类型。一个   integral constant expression是整数或未整数枚举类型的文字常量表达式。 [ 注意:   这些表达式可以用作数组边界(8.3.4,5.3.4),作为位字段长度(9.6),作为枚举器初始化器   如果底层类型不固定(7.2),则为空指针常量(4.10),并作为对齐(7.6.2)。 -结束   注意] T类型的转换常量表达式是文字常量表达式,隐式转换为T类型,   其中隐式转换(如果有)在文字常量表达式和隐式转换中是允许的   序列仅包含用户定义的转换,左值到右值转换(4.1),整数促销(4.5),   和整数转换(4.7),而不是缩小转换(8.5.4)。 [注意:可以使用这样的表达方式   作为case表达式(6.4.2),作为枚举器初始值设定项,如果基础类型是固定的(7.2),并作为整数或   枚举非类型模板参数(14.3)。 -end note]引用常量表达式是左值   核心常量表达式,用于指定具有静态存储持续时间或函数的对象。 一个地址   常量表达式是指针类型的prvalue核心常量表达式,其值为的地址   具有静态存储持续时间的对象,函数的地址,空指针值或prvalue核心   std :: nullptr_t类型的常量表达式。 统称为文字常量表达式,引用常量   表达式和地址常量表达式称为常量表达式。

核心常量表达式不是直接表达式,在第三段中还有其他条件。

答案 4 :(得分:1)

5.19p2没有定义常量表达式,它定义了核心常量表达式。

核心常量表达式只有符合5.19p3中的规则之一才会成为常量表达式。在那里,jrok已经指出了相关部分:

  

地址常量表达式是指针类型的prvalue核心常量表达式,它计算具有静态存储持续时间的对象的地址,函数的地址或空指针值,或者std::nullptr_t类型的prvalue核心常量表达式。

您的核心常量表达式&y不会评估其中任何一个,因此它不是地址常量表达式,因此不是常量表达式。 < / p>

答案 5 :(得分:0)

在C ++ pre-C ++ 11中:

  

其他表达式[比整数常量表达式]是   仅仅为了达到目的而考虑了恒定表达   非局部静态对象初始化(3.6.2)。这样   常量表达式应评估为以下之一:

     

[...]

     

- 地址常量表达式

     

[...]

     

地址常量表达式是指向左值的指针   指定静态存储持续时间的对象,字符串   文字(2.13.4)或函数。

由于y没有静态存储持续时间,&y不会 是一个不变的表达。

C ++ 11似乎改变了这一点;我怀疑这是一个 但是,过度。 (C ++ pre-C ++ 11列出了那些东西 常数表达式。 C ++ 11列出了没有的东西。它 人们很容易被遗忘。)

无论如何:你的比较在标准中是不可用的 C ++;比较两个不指向的地址的结果 进入同一个对象是未指定的。 (另一方面,我有 偶尔在机器相关代码中使用类似的东西。 不是静态的,而是在PC或Solaris上的Linux平台上, 可以确定指针是否指向对象 具有静态生命周期,自动变量或动态 用这种技巧分配内存。)

编辑:

paxdiablo的回答引用了我没有找到的段落 我读C ++ 11; C ++ 11遵循与C ++ pre-11相同的规则 在这方面,并为了成为一个恒定的地址 表达式,地址必须是具有静态的对象的地址 生命周期(或函数或空指针)。