考虑以下代码:
struct foo {
static constexpr const void* ptr = reinterpret_cast<const void*>(0x1);
};
auto main() -> int {
return 0;
}
上面的例子在g ++ v4.9(Live Demo)中编译得很好,而它无法在clang v3.4(Live Demo)中编译并生成以下错误:
错误:constexpr变量'ptr'必须由常量表达式初始化
问题:
TL; DR
clang
是正确的,这是已知的gcc
bug。您可以使用intptr_t
代替并在需要使用该值时进行强制转换,或者如果这样做不可行,那么gcc
和clang
都支持一些文档解决方案,应该允许您的特定用例。
细节
因此,如果我们去clang
部分,draft C++11 standard是正确的5.19
常量表达式第2段说:
条件表达式是核心常量表达式,除非它涉及以下之一作为潜在评估的子表达式[...]
并包括以下项目:
- reinterpret_cast(5.2.10);
一个简单的解决方案是使用intptr_t:
static constexpr intptr_t ptr = 0x1;
然后在需要使用时再投射:
reinterpret_cast<void*>(foo::ptr) ;
将它留在那可能很诱人,但这个故事虽然变得更有趣。这是知道并仍然开放gcc
bug看到Bug 49171: [C++0x][constexpr] Constant expressions support reinterpret_cast。从讨论中可以清楚地看出,gcc
开发人员有一些明确的用例:
我相信我在C ++ 03中可用的常量表达式中找到了reinterpret_cast的一致用法:
//---------------- struct X { X* operator&(); }; X x[2]; const bool p = (reinterpret_cast<X*>(&reinterpret_cast<char&>(x[1])) - reinterpret_cast<X*>(&reinterpret_cast<char&>(x[0]))) == sizeof(X); enum E { e = p }; // e should have a value equal to 1 //----------------
基本上这个程序演示了这个技术,C ++ 11库函数addressof基于并因此无条件地从核心语言中的常量表达式中排除reinterpret_cast会使这个有用的程序无效并且使得无法将addressof声明为constexpr函数。
但是无法为这些用例编写异常,请参阅closed issues 1384:
虽然在C ++ 03中的地址常量表达式中允许使用reinterpret_cast,但是这种限制已在某些编译器中实现,并且未证明会破坏大量代码。 CWG认为处理指纹改变的指针的复杂性(指针算术和取消引用不允许这样的指针)超过了放宽当前限制的可能效用。
但显然gcc
和clang
支持一个小文档扩展,允许使用__builtin_constant_p (exp)不断折叠非常量表达式,因此gcc
和clang
接受以下表达式:
static constexpr const void* ptr =
__builtin_constant_p( reinterpret_cast<const void*>(0x1) ) ?
reinterpret_cast<const void*>(0x1) : reinterpret_cast<const void*>(0x1) ;
找到这方面的文档几乎是不可能的,但这个带有以下片段的llvm commit is informative提供了一些有趣的阅读:
- 支持gcc __builtin_constant_p()? ...:...在C ++ 11中折叠黑客
和:
+ // __builtin_constant_p? :是神奇的,永远是一个潜在的常数。
和:
- //这个宏强制它的参数是不变的,即使它不是
- //否则是一个常量表达式。
- define fold(x)(__ builtin_constant_p(x)?(x):( x))
我们可以在gcc-patches电子邮件中找到关于此功能的更正式的解释:C constant expressions, VLAs etc. fixes说:
此外,实现中__builtin_constant_p调用作为条件表达式条件的规则比正式模型中的规则更宽松:条件表达式的选定一半完全折叠而不考虑它是否是正式表达式,因为__builtin_constant_p完全测试了折叠论证本身。
Clang是对的。重新解释的结果永远不是一个常量表达式(参见C ++ 11 5.19 / 2)。
常量表达式的目的是可以将它们作为值进行推理,并且值必须是有效的。你写的东西不是一个有效的指针(因为它不是一个对象的地址,或者通过指针算术与一个对象的地址相关),所以不允许你将它用作一个常量表达式。如果您只想存储数字1
,请将其存储为uintptr_t
并在使用网站上进行重新解释。
顺便说一句,要详细说明“有效指针”的概念,请考虑以下constexpr
指针:
int const a[10] = { 1 };
constexpr int * p1 = a + 5;
constexpr int b[10] = { 2 };
constexpr int const * p2 = b + 10;
// constexpr int const * p3 = b + 11; // Error, not a constant expression
// static_assert(*p1 == 0, "") // Error, not a constant expression
static_assert(p2[-2] == 0, ""); // OK
// static_assert(p2[1] == 0, ""); // Error, "p2[2] would have UB"
static_assert(p2 != nullptr, ""); // OK
// static_assert(p2 + 1 != nullptr, ""); // Error, "p2 + 1 would have UB"
p1
和p2
都是常数表达式。但是指针运算的结果是否是常量表达式取决于它是否不是UB!如果允许reinterpret_casts的值为常量表达式,则这种推理基本上是不可能的。