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

And*_*zos 17 c++ pointer-arithmetic language-lawyer constexpr c++11

以下C++ 11程序:

int x = 42;

void f()
{
        int y = 43;

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

int main()
{
        f();
}
Run Code Online (Sandbox Code Playgroud)

不抱怨使用gcc 4.7进行编译:

error: ‘&y’ is not a constant expression
Run Code Online (Sandbox Code Playgroud)

这符合我的直觉.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();
}
Run Code Online (Sandbox Code Playgroud)

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

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

dyp*_*dyp 10

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

[dcl.dcl]/1

static_assert-declaration:
static_assert (constant-expression ,string-literal) ;

[dcl.dcl/4

static_assert声明中,constant-expression应该是一个可以在上下文中转换为的常量表达式bool.

[expr.const/4

总的来说,字面常量表达式,引用常量表达式,并解决常量表达式被称为常量表达式.


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

[expr.const/4

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

的类型&x < &ybool按照[expr.rel]/1.

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

常量表达式是文本类型的prvalue芯常量表达式[...]

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


正如评论中的TemplateRexhvd指出的那样,在这种特殊情况下,&x < &y不满足核心常量表达式的要求:

[expr.const]/2

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

[expr.rel]/2

如果两个指针pq相同类型的点不属于相同对象的成员或相同数组的元素不同的对象或不同的功能,或者如果只有它们中的一个为空,结果p<q,p>q,p<=q,和p>=q未指定.

但是,举个例子

int arr[2] = {1, 2};
static_assert(&a[0] < &a[1], "");
Run Code Online (Sandbox Code Playgroud)

该表达式也a < a+1满足了这一要求.


pax*_*blo 7

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

y根据您的调用堆栈历史记录,地址可能会有很大差异.

C++11 5.19 Constant expressions州第3段规定了运营商地址可被视为常数表达式的条件(第2段中详述的核心常量表达式如何变为"真实"常量表达式):

...一个地址常量表达式是指针类型的计算结果为prvalue芯常量表达式具有静态存储持续时间的对象的地址,一个函数的地址,或一个空指针值,或者一个的prvalue芯常量表达式输入std :: nullptr_t.文字常量表达式,引用常量表达式和地址常量表达式统称为常量表达式.

既然&y不是那些东西,它就不被认为是一个恒定的表达.


Tem*_*Rex 5

获取某物的地址并不是这里的罪魁祸首,而是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();
}
Run Code Online (Sandbox Code Playgroud)

实例

只是为了表明这与 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();
}
Run Code Online (Sandbox Code Playgroud)

另一个活生生的例子

  • @JamesKanze 提出了一个问题:常量表达式是否可以由本身不是常量表达式的子表达式组成?标准是否明确禁止这样做?我认为 `&amp;x != &amp;y` 在编译时可以可靠地为真,因为两个不同的对象不能具有相同的地址。指针的实际值与该表达式的值无关,不是吗? (2认同)