constexpr基类的地址

jda*_*dls 6 c++ base-class constexpr c++14

使用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; 
Run Code Online (Sandbox Code Playgroud)

Edw*_*ard 5

是的,可以使用常量表达式计算基类的位移,但它根本不是可移植的.您可以使用clang也支持的鲜为人知但已记录的 gcc扩展名.它涉及__builtin_constant_p使用与操作员一起使用时?::

#define CB (&(B&)*(C*)nullptr)
constexpr auto c_b_address = __builtin_constant_p CB ? CB : CB;
Run Code Online (Sandbox Code Playgroud)

请注意,我刚刚使用宏CB来明确发生了什么.当然,这也可以通过多次重复表达来完成.顺便说一下,我首先在这个包含有用背景信息的问题中学到了这个技巧.

正如您可能已经理解的那样,基本问题是表达式reinterpret_cast中不允许使用等效的C样式转换constexpr.奇怪的是,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
Run Code Online (Sandbox Code Playgroud)

使用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
Run Code Online (Sandbox Code Playgroud)

在我运行Linux的64位机器上,只有CB1两个编译器才能得到4的正确答案.与海湾合作委员会,双方CB1CB2工作有或没有__builtin_constant_p.使用铛奏效的唯一版本是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.
Run Code Online (Sandbox Code Playgroud)

反过来,这指向gcc的Bugzilla bug 38377,它讨论了这个问题.具体来说,在2008年,这个错误报告为"__builtin_constant_p(t)?t:1不被视为常量整数表达式".在讨论中,注意到对于条件运算符(?:),

是的,这是一个(文档化的)特殊情况,需要与现有的GNU C代码兼容.

并进一步,

如果你对C做错了,那么GCC将无法引导,因为它是GCC在构建GCC时依赖的GNU C语义的一部分.

鉴于此,似乎行为既具体又刻意,并且因为gcc本身依赖于它,可能是一种相当稳定的行为.

尽管如此,关于使用非标准实现细节的所有常见警告都适用.如果你可以在运行时执行此操作,那么gcc和clang都可以接受:

ptrdiff_t cb = (char *)(&(B&)*(C*)nullptr) - (char *)nullptr;
Run Code Online (Sandbox Code Playgroud)