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

101*_*010 17 c++ gcc clang constexpr c++11

考虑以下代码:

struct foo {
  static constexpr const void* ptr = reinterpret_cast<const void*>(0x1);
};

auto main() -> int {
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

上面的例子在g ++ v4.9(Live Demo)中编译得很好,但它无法在clang v3.4(Live Demo)中编译并生成以下错误:

错误:constexpr变量'ptr'必须由常量表达式初始化

问题:

  • 根据标准,哪两个编译器是正确的?

  • 声明这种表达的正确方法是什么?

Sha*_*our 16

TL; DR

clang这是正确的,这是已知的gcc错误.您可以intptr_t在需要使用值时使用替代和强制转换,或者如果它不可行则可以使用这两者gccclang支持一些文档化的解决方法,应该允许您的特定用例.

细节

clang如果我们去C++ 11标准部分草案,那么在这一部分是正确的.5.19 常量表达式2段说:

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

并包括以下项目:

- reinterpret_cast(5.2.10);

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

static constexpr intptr_t ptr = 0x1;
Run Code Online (Sandbox Code Playgroud)

然后在需要使用时再投射:

reinterpret_cast<void*>(foo::ptr) ;
Run Code Online (Sandbox Code Playgroud)

将它留在那可能很诱人,但这个故事虽然变得更有趣.这是知道的,仍然是开放的gccbug见Bug 49171:[C++ 0x] [constexpr]常量表达式支持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
//----------------
Run Code Online (Sandbox Code Playgroud)

基本上这个程序演示了这个技术,C++ 11库函数addressof基于并因此无条件地从核心语言中的常量表达式中排除reinterpret_cast 会使这个有用的程序无效并且使得无法将addressof声明为constexpr函数.

但是无法为这些用例编写异常,请参阅已关闭的问题1384:

虽然在C++ 03中的地址常量表达式中允许使用reinterpret_cast,但是这种限制已在某些编译器中实现,并且未证明会破坏大量代码.CWG认为处理指纹改变的指针的复杂性(指针算术和取消引用不允许这样的指针)超过了放宽当前限制的可能效用.

BUT显然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)  ;
Run Code Online (Sandbox Code Playgroud)

为此找到文档几乎是不可能的,但是这个llvm提交提供了丰富的信息,下面的代码片段提供了一些有趣的阅读:

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

和:

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

和:

  • //这个宏强制它的参数是不变的,即使它不是
  • //否则是一个常量表达式.
  • define fold(x)(__ builtin_constant_p(x)?(x):( x))

我们可以在gcc-patches电子邮件中找到关于此功能的更正式的解释:C常量表达式,VLA等修复,其中说:

此外,实现中__builtin_constant_p调用作为条件表达式条件的规则比正式模型中的规则更宽松:条件表达式的选定一半完全折叠而不考虑它是否是正式表达式,因为__builtin_constant_p完全测试了折叠论证本身.

  • 什么不能是'constexpr`的任意例外列表开始惹恼我...... (6认同)

Ker*_* SB 12

Clang是对的.重新解释的结果永远不是一个常量表达式(参见C++ 11 5.19/2).

常量表达式的目的是可以将它们作为值进行推理,并且值必须是有效的.你写的东西不是一个有效的指针(因为它不是一个对象的地址,或者通过指针算术与一个对象的地址相关),所以不允许你将它用作一个常量表达式.如果您只想存储该号码1,请将其存储为a uintptr_t并在使用站点进行重新解释.


顺便说一句,要详细说明"有效指针"的概念,请考虑以下constexpr指针:

constexpr int const a[10] = { 1 };
constexpr int * p1 = a + 5;

constexpr int const b[10] = { 2 };
constexpr int const * p2 = b + 10;

// constexpr int const * p3 = b + 11;    // Error, not a constant expression

static_assert(*p1 == 0, "");             // OK

// static_assert(p1[5] == 0, "");        // Error, not a constant expression

static_assert(p2[-2] == 0, "");          // OK

// static_assert(p2[1] == 0, "");        // Error, "p2[1]" would have UB

static_assert(p2 != nullptr, "");        // OK

// static_assert(p2 + 1 != nullptr, ""); // Error, "p2 + 1" would have UB
Run Code Online (Sandbox Code Playgroud)

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

  • 答案中的第一段无疑是有效的.第二段,不是那么多.Deduplicator绝对正确.它是系统上的有效指针,其中"0x1"是内存中字节的地址,尽管它不是常量表达式. (3认同)
  • 你怎么知道这不是一个有效的指针?在我的,它指向地址1,这是完全正常的.(微控制器) (2认同)
  • @Deduplicator:如果你真的需要引用,而不仅仅是引用,那么标准的引用子句说"A*conditional-expression*是*核心常量表达式*,除非它涉及以下之一",其中以下列表包括"a`reinterpret_cast`" (2认同)
  • @Deduplicator:*常量表达式*是*核心常量表达式*的子集,如以下第5.19/3段所述. (2认同)