这段代码有效:
struct Blob {
static constexpr int a = 10;
};
int main() {
Blob b;
auto c = b.a;
}
但是,如果我将int
更改为float
,则会出现错误:
struct Blob {
static constexpr float a = 10.0f;
};
/tmp/main-272d80.o:在函数
main': main.cpp:(.text+0xe): undefined reference to
Blob::a'
为什么我不能以这种方式使用constexpr float
?
编译器:Ubuntu clang 版本 3.5.0-4ubuntu2 (tags/RELEASE_350/final)
在 gcc 版本 4.9.1(Ubuntu 4.9.1-16ubuntu6)上测试,没有错误。
编辑:
如果我使用 -O1、-O2、-O3 或 -Os 但因 -O0 而失败,它将编译
C++11 读取
名称显示为潜在计算表达式的变量是 ODR 使用,除非它是满足以下要求的对象 出现在常量表达式 (5.19) 和左值到右值中 转换 (4.1) 立即应用。
显然,l-t-r 转换会立即应用,浮点类型的constexpr
变量可以按照 [expr.const]/(2.7.1) 出现在常量表达式中:
条件表达式是核心常量表达式,除非它 涉及以下之一作为潜在评估的子表达式 [..]
- 左值到重值的转换 (4.1),除非它应用于
- 文字类型的 glvalue 引用用
constexpr
定义的非易失性对象,或引用此类对象的子对象 对象,或
似乎是叮当虫。
,如果我们改用Blob::a
,clang
不会抱怨:
auto c = Blob::a;
这对于确定它是否使用 odr 无关紧要。所以这看起来像一个clang
错误,我可以在 clang 3.7 上重现它,而无需优化。我们可以说这是一个 odr 问题,因为添加类外定义可以解决问题(实时查看):
constexpr float Blob::a ;
那么什么时候需要定义静态 constexpr 类成员呢?这在第 9.4.2
节 [class.static.data] 中有所介绍,其中说(强调我的):
文本类型的静态数据成员可以在 使用 constexpr 说明符进行类定义;如果是这样,其声明应指定大括号或等于初始值设定项 其中作为赋值表达式的每个初始值设定项子句都是一个常量表达式。[ 注意:在两者中 在这些情况下,成员可能出现在常量表达式中。—尾注 ] 成员仍应定义 在命名空间作用域中,如果它在程序中是 ODR 使用的 (3.2) 并且命名空间作用域定义不应 包含初始值设定项。
如果它是 odr 使用的,它需要一个定义。它是ODR使用的吗?不,不是。第3.2
节[basic.def.odr]中最初的C++11措辞说:
表达式可能会被计算,除非它是未计算的操作数(条款 5)或子表达式 其中。名称显示为潜在计算表达式的变量是 odr 使用的,除非它是 满足出现在常量表达式 (5.19) 和左值到右值中的要求的对象 转换 (4.1) 立即应用。
a
同时满足这两个条件,则它是一个常量表达式,并立即应用左值到右值的转换。缺陷报告 712 更改了适用于 C++11 的措辞,因为它是一份缺陷报告,3.2
现在说:
名称显示为潜在计算表达式 ex 的变量 x 是 odr 使用的,除非应用 左值到重值的转换 (4.1) 到 x 产生一个常量表达式 (5.19),该表达式不会调用任何非平凡 函数,如果 x 是一个对象,则 ex 是表达式 e 的潜在结果集合中的一个元素,其中 左值到重值转换 (4.1) 应用于 E,或者 E 是丢弃值表达式
匹配的潜在结果将是:
如果 e 是 id 表达式 (5.1.1),则集合仅包含 e。
它是一个常量表达式,并且应用了左值到右值的转换,因此不使用它。