Using clang 3.4 (trunk), is there any way to calculate the displacement of a base class with a constant expression?
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;
In other words, you should use constexpr for your constants in header files, if possible, otherwise const . And if you require the address of that constant to be the same everywhere mark it as inline .
constexpr indicates that the value, or return value, is constant and, where possible, is computed at compile time. A constexpr integral value can be used wherever a const integer is required, such as in template arguments and array declarations.
A static constexpr variable has to be set at compilation, because its lifetime is the the whole program. Without the static keyword, the compiler isn't bound to set the value at compilation, and could decide to set it later.
A constexpr function is a function that can be invoked within a constant expression. A constexpr function must satisfy the following conditions: It is not virtual. Its return type is a literal type. Each of its parameters must be of a literal type.
A constexpr function must satisfy the following requirements: for constructor and destructor (since C++20), the class must have no virtual base classes
static constexpr int const& x = 42; // constexpr reference to a const int object // (the object has static storage duration // due to life extension by a static reference) Even though try blocks and inline assembly are allowed in constexpr functions, throwing exceptions or executing the assembly is still disallowed in a constant expression.
A constructor that is declared with a constexpr specifier is a constexpr constructor. Previously, only expressions of built-in types could be valid constant expressions. With constexpr constructors, objects of user-defined types can be included in valid constant expressions.
Not initialized int j = 0; constexpr int k = j + 1; //Error! j not a constant expression A constexpr function is one whose return value is computable at compile time when consuming code requires it. Consuming code requires the return value at compile time to initialize a constexpr variable, or to provide a non-type template argument.
Yes, it's possible to calculate the displacement of a base class with a constant expression, but it's not at all portable. You can use a little-known but documented gcc extension that is also supported by clang. It involves the use of __builtin_constant_p
when used with the operator ?:
:
#define CB (&(B&)*(C*)nullptr)
constexpr auto c_b_address = __builtin_constant_p CB ? CB : CB;
Note that I've just used the macro CB to make it clear what was happening. This could also, of course, be done by repeating the expression multiple times. Incidentally, I first learned of this trick in this question which contains useful background information.
The basic problem, as you probably already understand, is that neither a reinterpret_cast
nor the equivalent C-style cast are allowed in constexpr
expressions. Curiously, the C-style cast (as above) is accepted, but a reinterpret_cast
(which would generate no code) is not. I also experimented with the obscure but seemingly appropriate ->*
operator but with mixed results:
#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
Results with g++ 4.8.3 and 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
On my 64-bit machine running Linux, only CB1
yields the correct answer of 4 with both compilers. With gcc, both CB1
and CB2
work with or without __builtin_constant_p
. Using clang the only version that worked was CB1
with __builtin_constant_p
.
As @ShafikYaghmour quite reasonably asks in a comment, "do you have a gcc or clang reference that says that they support this behavior?" I'm going to broaden this to ask "what documentation exists that indicates this is intentional and not just an odd side effect?" After all, if someone is actually going to use this, it would be nice to have some indication that it might actually continue to exist in the future. This section attempts to address that concern.
For clang, the reference is the source code itself in which a comment in the VisitConditionalOperator
function which says:
// 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.
This in turn, points to gcc's Bugzilla bug 38377 which discusses this issue. Specifically, in 2008 this bug was reported as "__builtin_constant_p(t) ? t : 1 is not considered a constant integer expression". In the discussion, it's noted that for the conditional operator (?:
),
Yes, this is a (documented) special case required to be compatible with existing GNU C code.
And further,
If you get this wrong for C then GCC will fail to bootstrap, as it's part of the GNU C semantics GCC relies on when being built with GCC.
Given that, it seems that the behavior is both specific and deliberate, and because gcc itself relies on it, probably a fairly stable behavior.
Still, all the usual caveats about using non-standard implementation details apply. If you can perform this at runtime instead, it becomes acceptable to both gcc and clang:
ptrdiff_t cb = (char *)(&(B&)*(C*)nullptr) - (char *)nullptr;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With