以下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
的比较。
答案 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 TemplateRex和hvd,在这种特殊情况下,&x < &y
无法满足核心常量表达式的要求:
[expr.const] / 2
[核心常量表达式不得包含]关系或相等运算符,其中结果未指定;
[expr.rel] / 2
如果同一类型的两个指针
p
和q
指向不是同一对象的成员或同一数组的元素或不同函数的不同对象,或者只指向其中一个为空,p<q
,p>q
,p<=q
和p>=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();
}
只是为了表明这与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相同的规则 在这方面,并为了成为一个恒定的地址 表达式,地址必须是具有静态的对象的地址 生命周期(或函数或空指针)。