constexpr和初始化一个静态const void指针reinterpret强制转换,哪个编译器是正确的?

考虑下面的一段代码:

struct foo { static constexpr const void* ptr = reinterpret_cast<const void*>(0x1); }; auto main() -> int { return 0; } 

上面的例子在g ++ v4.9( Live Demo )中编译得很好,但是在v3.4( Live Demo )中编译失败,并且产生下面的错误:

错误:constexprvariables'ptr'必须由一个常量expression式初始化

问题:

  • 两个编译器哪一个按照标准是正确的?

  • 宣称这种expression方式的正确方法是什么?

TL; DR

clang是正确的,这是已知的gcc错误。 您可以使用intptr_t来代替,并在需要使用该值时进行intptr_t ,或者如果这不可行,那么gccclang支持一个小的文档化的变通办法,这应该允许您的特定用例。

细节

所以,如果我们转到C ++ 11标准部分的草案, clang是正确的5.19 常量expression式2段说:

一个条件expression式是一个核心常量expression式,除非它涉及以下之一作为潜在的评估子expression式[…]

并包含以下内容:

– 一个reinterpret_cast(5.2.10);

一个简单的解决scheme是使用intptr_t :

 static constexpr intptr_t ptr = 0x1; 

然后在需要使用时再施放:

 reinterpret_cast<void*>(foo::ptr) ; 

这可能是诱人的,但这个故事变得更有趣了。 这是已知的,仍然是开放的海湾合作委员会的错误见错误49171:[C ++ 0x] [constexpr]常量expression式支持reinterpret_cast 。 从讨论中可以清楚地看出, gcc开发者对此有一些明确的用例:

我相信我发现在C ++ 03中可用的常量expression式中使用了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是基于的,因此从核心语言的常量expression式中无条件地排除reinterpret_cast会使这个有用的程序无效,并且使得将addressof声明为constexpr函数是不可能的。

但是无法为这些用例获得例外,请参见封闭式问题1384 :

虽然reinterpret_cast在C ++ 03的地址常量expression式中是允许的,但是这个限制已经在一些编译器中实现了,并且没有certificate会破坏大量的代码。 CWG认为,处理指针变化的指针(指针算术和取消引用不能在这样的指针上被允许)的复杂性超过了放松当前限制的可能效用。

显然gccclang支持一个有点文档的扩展,它允许使用__builtin_constant_p(exp)不断折叠非常量expression式,所以gccclang都可以接受下列expression式:

 static constexpr const void* ptr = __builtin_constant_p( reinterpret_cast<const void*>(0x1) ) ? reinterpret_cast<const void*>(0x1) : reinterpret_cast<const void*>(0x1) ; 

find这个文件几乎是不可能的,但是这个llvm提交是提供信息 ,下面的片段提供了一些有趣的阅读:

  • 支持gcc __builtin_constant_p()? …:…在C ++ 11中折叠黑客

和:

+ // __builtin_constant_p? :是神奇的,永远是一个潜在的常数。

和:

  • //这个macros强制它的参数是不变的,即使它不是
  • //否则是一个常量expression式。
  • 定义fold(x)(__builtin_constant_p(x)?(x):(x))

我们可以在gcc-patches电子邮件中find这个function的更正式的解释: C常量expression式,VLA等修正 ,它说:

此外,__builtin_constant_p作为条件expression式条件在实现中的规则比forms化模型中的条件expression式条件更宽松:条件expression式的选定一半完全折叠,而不考虑它是否是forms上的常量expression式,因为__builtin_constant_p完全testing折叠的论点本身。

铿锵是对的。 重新parsing的结果永远不会是一个常量expression式(参见C ++ 11 5.19 / 2)。

常量expression式的目的是可以将它们作为值推理,值必须是有效的。 你写的东西并不是一个有效的指针(因为它不是一个对象的地址,或者是通过指针算术与一个对象的地址相关),所以你不能把它用作常量expression式。 如果你只是想存储数字1 ,把它作为一个uintptr_t存储,然后在使用地点进行重新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" 

p1p2都是常量expression式。 但是指针运算的结果是否是一个常量expression式取决于它是不是UB! 如果您允许reinterpret_casts的值为常量expression式,这种推理实质上是不可能的。