20

以下 C++11 程序:

int x = 42;

void f()
{
        int y = 43;

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

int main()
{
        f();
}

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

error: ‘&amp;y’ is not a constant expression

这符合我的直觉。的地址y可能随着每次调用而改变f,所以在翻译过程中当然不能计算出来。

然而,5.19 [expr.const] 中的所有要点似乎都没有阻止它成为一个常量表达式。

我看到的仅有的两个竞争者是:

左值到右值的转换...

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

anid-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

4

6 回答 6

12

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

[dcl.dcl]/1

static_assert-declaration:
static_assert (常量表达式,字符串文字) ;

[dcl.dcl]/4

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

[expr.const]/4

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


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

[expr.const]/4

地址常量表达式是类型std::nullptr_t或指针类型 [...]的纯右值核心常量表达式(根据上下文要求进行转换后)。

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

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

文字常量表达式是文字类型的纯右值核心常量表达式 [... ]

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


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

[expr.const]/2

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

[expr.rel]/2

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

但是,例如

int arr[2] = {1, 2};
static_assert(&a[0] < &a[1], "");

该表达式a < a+1也满足此要求。

于 2013-08-21T09:30:47.523 回答
7

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

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

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

...地址常量表达式是指针类型的纯右值核心常量表达式,其计算结果为具有静态存储持续时间的对象地址、函数的地址、空指针值或纯右值核心常量表达式键入 std::nullptr_t。字面常量表达式、引用常量表达式和地址常量表达式统称为常量表达式。

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

于 2013-08-21T08:29:20.677 回答
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();
}

活生生的例子

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

另一个活生生的例子

于 2013-08-21T08:39:54.237 回答
1

抱歉,我同意之前的答案可能是对项目的错误阅读。相反,实际的相关条款是 5.19 [expr.const] 第 3 段,内容如下(添加了突出显示):

文字常量表达式是文字类型的纯右值核心常量表达式,但不是指针类型. 整型常量表达式是整型或无范围枚举类型的字面常量表达式。[ 注意:此类表达式可用作数组边界 (8.3.4, 5.3.4)、位域长度 (9.6)、如果基础类型不固定 (7.2) 则用作枚举器初始化器、空指针常量 (4.10 ),并作为对齐方式 (7.6.2)。—尾注] T 类型的已转换常量表达式是字面常量表达式,隐式转换为 T 类型,其中隐式转换(如果有)在字面常量表达式中是允许的,并且隐式转换序列仅包含用户定义的转换,左值到右值的转换 (4.1)、整数提升 (4.5) 和除缩小转换 (8.5.4) 之外的整数转换 (4.7)。[注:此类表达式可用作案例表达式(6.4.2),如果基础类型是固定的(7.2),则作为枚举器初始化器,以及作为整数或枚举非类型模板参数(14.3)。—尾注] 引用常量表达式是一个左值核心常量表达式,它指定具有静态存储持续时间的对象或函数。地址常量表达式是指针类型的纯右值核心常量表达式,其计算结果为具有静态存储持续时间的对象的地址、函数的地址、空指针值或 std 类型的纯右值核心常量表达式: :nullptr_t。字面常量表达式、引用常量表达式和地址常量表达式统称为常量表达式。

核心常量表达式不是直接的常量表达式,第三段中详细说明了其他条件。

于 2013-08-21T08:40:05.997 回答
1

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

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

地址常量表达式是指针类型的纯右值核心常量表达式,其计算结果为具有静态存储持续时间的对象的地址、函数的地址、空指针值或类型的纯右值核心常量表达式std::nullptr_t

您的核心常量表达式&y不会对其中任何一个求值,因此它不是地址常量表达式,因此也不是常量表达式。

于 2013-08-21T08:48:09.637 回答
0

在 C++ 前 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 相同的规则,并且为了成为常量地址表达式,地址必须是具有静态生命周期的对象(或函数,或空指针)的地址。

于 2013-08-21T08:45:23.587 回答