【问题标题】:constexpr address of base classconstexpr 基类地址
【发布时间】:2013-11-26 11:22:19
【问题描述】:

使用clang 3.4(trunk),有没有办法用常量表达式计算基类的位移?

struct A { int a; };
struct B { int b; };

struct C: A, B {};

// cannot access base class of null pointer:
constexpr auto c_b_address = /*(size_t)*/ &(B&) *(C*)nullptr; 

【问题讨论】:

  • 我唯一能想到的:你为什么要那样做?
  • 我正在研究一个反射模型,以允许在成员和基类之间导航。最好在编译时构造元数据。
  • 位移是一个以字节为单位的数字。这需要将两个指针都减去char*,而这又需要reinterpret_cast。如果没有像offsetof 这样的技巧,那我怀疑你能成功。
  • @dyp 问题是,为什么不能在编译时完成offsetof
  • @TemplateRex 我不认为在编译时禁止使用它,只是不能保证它可以在常量表达式中使用。潜在的问题可能是常量表达式中的指针算术(参见例如CWG 1313)。

标签: c++ base-class constexpr c++14


【解决方案1】:

是的,可以用常量表达式计算基类的位移,但它根本不是可移植的。您可以使用一个鲜为人知但 documented gcc 扩展,它也受 clang 支持。涉及到__builtin_constant_p与运算符?:一起使用时的使用:

#define CB (&(B&)*(C*)nullptr)
constexpr auto c_b_address = __builtin_constant_p CB ? CB : CB;

请注意,我刚刚使用宏 CB 来说明发生了什么。当然,这也可以通过多次重复表达式来完成。顺便说一句,我第一次知道这个技巧in this question,其中包含有用的背景信息。

您可能已经了解,基本问题是constexpr 表达式中既不允许使用reinterpret_cast,也不允许使用等效的C 风格转换。奇怪的是,C 风格的演员表(如上)被接受,但 reinterpret_cast(不会生成任何代码)不被接受。我还尝试了晦涩但看似合适的->* 运算符,但结果好坏参半:

#define CB1 (&(B&)*(C*)nullptr)
#define CB2 (&((reinterpret_cast<C*>(nullptr))->*(&C::b)))
#define CB3 (&(((C*)(nullptr))->*(&C::b)))
#define CB4 (&(B&)*reinterpret_cast<C*>(nullptr))
#define CB CB1

g++ 4.8.3 和 clang++ 3.4 的结果:

     g++             clang++
--- ------------    --------
CB1  OK              OK
CB2  compile error   compile error
CB3  OK              compiles but gives answer = 0
CB4  compile error   compile error

在我运行 Linux 的 64 位机器上,只有CB1 使用两种编译器产生正确答案 4。使用 gcc,CB1CB2 都可以使用或不使用 __builtin_constant_p。使用 clang 唯一有效的版本是 CB1__builtin_constant_p

我们为什么要相信这是故意行为?

正如@ShafikYaghmour 在评论中相当合理地询问的那样,“您是否有 gcc 或 clang 参考表明他们支持这种行为?”我将把它扩大到问“存在哪些文件表明这是故意的,而不仅仅是一个奇怪的副作用?”毕竟,如果有人真的要使用它,最好有一些迹象表明它将来可能会继续存在。本节试图解决这个问题。

对于 clang,引用是 the source code itself,其中 VisitConditionalOperator 函数中的注释说:

// If the condition (ignoring parens) is a __builtin_constant_p call,
// the result is a constant expression if it can be folded without
// side-effects. This is an important GNU extension. See GCC PR38377
// for discussion.

这又指向讨论此问题的 gcc 的 Bugzilla bug 38377。具体来说,在 2008 年,此错误被报告为“__builtin_constant_p(t) ? t : 1 is not视为常量整数表达式”。在讨论中,注意到对于条件运算符 (?:),

是的,这是一个(记录在案的)特殊情况,需要兼容 现有的 GNU C 代码。

还有,

如果您对 C 有此错误,则 GCC 将无法引导,因为它是 GCC 在使用 GCC 构建时所依赖的 GNU C 语义的一部分。

鉴于此,该行为似乎既具体又经过深思熟虑,并且由于 gcc 本身依赖于它,因此可能是一种相当稳定的行为。

不过,关于使用非标准实现细节的所有常见警告仍然适用。如果您可以在运行时执行此操作,则 gcc 和 clang 都可以接受:

ptrdiff_t cb = (char *)(&(B&)*(C*)nullptr) - (char *)nullptr;

【讨论】:

  • +1,所以它是非标准的,不应该依赖它
  • "但是等价的 reinterpret_cast 不是" -- 我可能遗漏了一些东西,但你的 C 风格转换不是都等价于 static_casts 吗?
  • @hvd: reinterpret_cast&lt;C*&gt; 只是说,“相信我,编译器,这东西真的是 C*,而 static_cast 实际上可能会调用用户定义的或编译器提供的转换. 为此,我们想要一个reinterpret_cast
  • @Edward C 风格转换的规则说(C*)x 表示static_cast&lt;C*&gt;(x),当它有效时(甚至有时它无效)。因此,如果static_cast&lt;C*&gt;(x) 有效,那么(C*)x 自动 等同于reinterpret_cast&lt;C*&gt;(x),即使在极少数情况下reinterpret_cast 是您真正想要的。使用static_cast,您的所有转化似乎都有效。
  • @ShafikYaghmour:如果我们理解在这种情况下“未定义的行为”意味着“标准未定义”,那么是的,这是一个公平的改写。
猜你喜欢
  • 2019-09-06
  • 1970-01-01
  • 2021-11-02
  • 2011-01-11
  • 2020-06-22
  • 2013-08-20
  • 1970-01-01
  • 2013-09-14
  • 2017-10-06
相关资源
最近更新 更多