constexpr 和使用重新解释强制转换的静态 const void 指针的初始化,哪个编译器是正确的?

2021-12-18 00:00:00 gcc clang c++ c++11 constexpr

考虑以下代码:

struct foo {static constexpr const void* ptr = reinterpret_cast(0x1);};自动主()->内部{返回0;}

以上示例在 g++ v4.9(Live Demo)中编译良好,但在clang v3.4 (Live Demo) 并生成以下错误:

<块引用>

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

问题:

  • 根据标准,这两种编译器中哪一种是正确的?

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

解决方案

TL;DR

clang 是正确的,这是已知的 gcc 错误.您可以使用 intptr_t 代替并在需要使用该值时进行转换,或者如果这不可行,则 gccclang 都支持一点记录在案的解决方法应该允许您的特定用例.

详情

所以如果我们转到 clang 是正确的/n3337.pdf" rel="nofollow noreferrer">草案 C++11 标准 部分 5.19 常量表达式 段落 2 说:

<块引用>

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

并包括以下项目符号:

<块引用>

――reinterpret_cast (5.2.10);

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

static constexpr intptr_t ptr = 0x1;

然后在您需要使用它时投射:

reinterpret_cast(foo::ptr) ;

可能很想就此搁笔,但这个故事变得更有趣了.这是已知的并且仍然打开 gcc 错误,请参阅 错误 49171: [C++0x][constexpr] 常量表达式支持 reinterpret_cast.从讨论中可以清楚地看出,gcc 开发人员对此有一些明确的用例:

<块引用>

我相信我在常量中找到了 reinterpret_cast 的一致用法C++03 中可用的表达式:

//---------------- struct X { X* operator&();};X x[2];const bool p = (reinterpret_cast(&reinterpret_cast(x[1]))- reinterpret_cast<X*>(&reinterpret_cast<char&>(x[0]))) == sizeof(X);枚举 E { e = p };//e 的值应该等于 1//----------------

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

但无法为这些用例创建异常,请参阅 已关闭问题 1384:

<块引用>

虽然在地址常量中允许 reinterpret_castC++03 中的表达式,这个限制已经在一些编译器,并没有被证明会破坏大量的代码.工作组认为处理指针的复杂性已更改(指针算术和取消引用不能被允许在这样的指针)超过了放松当前的可能效用限制.

BUT 显然 gccclang 支持一个小的文档扩展,允许使用 __builtin_constant_p (exp) 所以下面的表达式被 gccclang:

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

为此查找文档几乎是不可能的,但这 llvm commit 信息丰富,以下片段提供了一些有趣的阅读:

<块引用>

支持 gcc __builtin_constant_p() 吗?... : ... C++11 中的折叠 hack

和:

<块引用>

//__builtin_constant_p ?: 是神奇的,并且始终是一个潜在的常数.

和:

<块引用>

//这个宏强制它的参数是常量折叠的,即使它不是//否则为常量表达式.#define fold(x) (__builtin_constant_p(x) ? (x) : (x))

我们可以在 gcc-patches 电子邮件中找到对这个特性的更正式的解释:C 常量表达式、VLA 等修复,其中说:

<块引用>

此外,__builtin_constant_p 调用的规则是有条件的实现中的表达式条件比那些更宽松在正式模型中:条件表达式中选定的一半完全折叠而不考虑它是否形式上是常数表达式,因为 __builtin_constant_p 测试一个完全折叠的参数自己.

Consider the following piece of code:

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

auto main() -> int {
  return 0;
}

The above example compiles fine in g++ v4.9 (Live Demo), while it fails to compile in clang v3.4 (Live Demo) and generates the following error:

error: constexpr variable 'ptr' must be initialized by a constant expression

Questions:

  • Which of the two compilers is right according to the standard?

  • What's the proper way of declaring an expression of such kind?

解决方案

TL;DR

clang is correct, this is known gcc bug. You can either use intptr_t instead and cast when you need to use the value or if that is not workable then both gcc and clang support a little documented work-around that should allow your particular use case.

Details

So clang is correct on this one if we go to the draft C++11 standard section 5.19 Constant expressions paragraph 2 says:

A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression [...]

and includes the following bullet:

― a reinterpret_cast (5.2.10);

One simple solution would be to use intptr_t:

static constexpr intptr_t ptr = 0x1;

and then cast later on when you need to use it:

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

It may be tempting to leave it at that but this story gets more interesting though. This is know and still open gcc bug see Bug 49171: [C++0x][constexpr] Constant expressions support reinterpret_cast. It is clear from the discussion that gcc devs have some clear use cases for this:

I believe I found a conforming usage of reinterpret_cast in constant expressions useable in C++03:

//---------------- 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
//----------------

Basically this program demonstrates the technique, the C++11 library function addressof is based on and thus excluding reinterpret_cast unconditionally from constant expressions in the core language would render this useful program invalid and would make it impossible to declare addressof as a constexpr function.

but were not able to get an exception carved for these use cases, see closed issues 1384:

Although reinterpret_cast was permitted in address constant expressions in C++03, this restriction has been implemented in some compilers and has not proved to break significant amounts of code. CWG deemed that the complications of dealing with pointers whose tpes changed (pointer arithmetic and dereference could not be permitted on such pointers) outweighed the possible utility of relaxing the current restriction.

BUT apparently gcc and clang support a little documented extension that allows constant folding of non-constant expressions using __builtin_constant_p (exp) and so the following expressions is accepted by both gcc and clang:

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

Finding documentation for this is near impossible but this llvm commit is informative with the following snippets provide for some interesting reading:

support the gcc __builtin_constant_p() ? ... : ... folding hack in C++11

and:

// __builtin_constant_p ? : is magical, and is always a potential constant.

and:

// This macro forces its argument to be constant-folded, even if it's not
// otherwise a constant expression.
#define fold(x) (__builtin_constant_p(x) ? (x) : (x))

We can find a more formal explanation of this feature in the gcc-patches email: C constant expressions, VLAs etc. fixes which says:

Furthermore, the rules for __builtin_constant_p calls as conditional expression condition in the implementation are more relaxed than those in the formal model: the selected half of the conditional expression is fully folded without regard to whether it is formally a constant expression, since __builtin_constant_p tests a fully folded argument itself.

相关文章