使用clang 3.4(主干),有没有办法用常量表达式计算基类的位移?
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;
使用clang 3.4(主干),有没有办法用常量表达式计算基类的位移?
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;
是的,可以使用常量表达式计算基类的位移,但它根本不是可移植的。您可以使用一个鲜为人知但记录在案的gcc 扩展,它也受 clang 支持。它涉及__builtin_constant_p
与运算符一起使用时的使用?:
:
#define CB (&(B&)*(C*)nullptr)
constexpr auto c_b_address = __builtin_constant_p CB ? CB : CB;
请注意,我刚刚使用宏 CB 来说明发生了什么。当然,这也可以通过多次重复表达式来完成。顺便说一句,我首先在这个问题中了解到这个技巧,其中包含有用的背景信息。
正如您可能已经理解的那样,基本问题是表达式中既不允许 areinterpret_cast
也不允许等效的 C 样式转换。constexpr
奇怪的是,C 风格的演员表(如上)被接受,但 a 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,无论CB1
是否CB2
使用__builtin_constant_p
. 使用 clang 的唯一版本CB1
是__builtin_constant_p
.
正如@ShafikYaghmour 在评论中相当合理地问的那样,“您是否有 gcc 或 clang 参考表明他们支持这种行为?” 我将把它扩大到问“存在哪些文件表明这是故意的,而不仅仅是一个奇怪的副作用?” 毕竟,如果有人真的要使用它,最好有一些迹象表明它可能在未来继续存在。本节试图解决这个问题。
对于 clang,引用是源代码本身,其中函数中的注释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错误 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;