constexpr和一个带有重新解释强制转换的静态const void指针的初始化,哪个编译器是对的?

问题描述 投票:16回答:2

考虑以下代码:

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'必须由常量表达式初始化

问题:

  • 根据标准,哪两个编译器是正确的?
  • 声明这种表达的正确方法是什么?
c++ gcc c++11 clang constexpr
2个回答
14
投票

TL; DR

clang是正确的,这是已知的gcc bug。您可以使用intptr_t代替并在需要使用该值时进行强制转换,或者如果这样做不可行,那么gccclang都支持一些文档解决方案,应该允许您的特定用例。

细节

因此,如果我们去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认为处理指纹改变的指针的复杂性(指针算术和取消引用不允许这样的指针)超过了放宽当前限制的可能效用。

但显然gccclang支持一个小文档扩展,允许使用__builtin_constant_p (exp)不断折叠非常量表达式,因此gccclang接受以下表达式:

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完全测试了折叠论证本身。


11
投票

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"

p1p2都是常数表达式。但是指针运算的结果是否是常量表达式取决于它是否不是UB!如果允许reinterpret_casts的值为常量表达式,则这种推理基本上是不可能的。

© www.soinside.com 2019 - 2024. All rights reserved.