6

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; 
ildjarn
  • 62,044
  • 9
  • 127
  • 211
jdavidls
  • 348
  • 1
  • 5
  • 2
    The only thing I can think of: _why would you do that?_ – stefan Nov 26 '13 at 15:11
  • I'm working on a reflective model to allow navigating between members and base classes. is preferable to construct the metadata at compile time. – jdavidls Nov 26 '13 at 23:16
  • 1
    The displacement is a number in units of *bytes*. This requires subtracting both pointers as `char*`, which in turn requires a `reinterpret_cast`. If there's not some trick like `offsetof`, then I doubt you can pull it off. – dyp Aug 04 '14 at 10:47
  • @dyp the question is, why can't `offsetof` be done at compile-time? – TemplateRex Aug 04 '14 at 11:22
  • @TemplateRex I don't think it's forbidden to be used at compile-time, it's just not guaranteed to be usable in a constant expression. The underlying issue is probably pointer arithmetics in constant expressions (see e.g. [CWG 1313](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1313)). – dyp Aug 04 '14 at 11:37
  • @dyp `gcc` and `clang` provide a little documented way to use things like `reinterpret_cast` in a constexpr. I discuss it in my answer [here](http://stackoverflow.com/questions/24398102/constexpr-and-initialization-of-a-static-const-void-pointer-with-reinterpret-cas/24400015#24400015). I don't see how you do the pointer math without invoking undefined behavior though. – Shafik Yaghmour Aug 05 '14 at 03:57

1 Answers1

5

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.

Why should we believe this is intentional behavior?

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;
Community
  • 1
  • 1
Edward
  • 6,964
  • 2
  • 29
  • 55
  • +1, so it's non-standard andone shouldn't rely on it – TemplateRex Aug 06 '14 at 16:42
  • "but the equivalent `reinterpret_cast` is not" -- I may be missing something, but aren't your C-style casts all equivalent to `static_cast`s? –  Aug 06 '14 at 16:58
  • @hvd: A `reinterpret_cast` just says, "trust me, compiler, this thing is really a `C*`, while a `static_cast` might actually invoke a user-defined or compiler-supplied conversion. For this purpose, we want a `reinterpret_cast`. – Edward Aug 06 '14 at 17:14
  • @Edward The rules for a C-style cast say that `(C*)x` means `static_cast(x)`, when that is valid (and even some times when it is not valid). So if `static_cast(x)` is valid, then `(C*)x` automatically is *not* equivalent to `reinterpret_cast(x)`, even in the rare case where `reinterpret_cast` is what you really want. And all of your conversions seem valid with `static_cast`. –  Aug 06 '14 at 17:23
  • @hvd: I see what you mean. Semantically I was going *from* `reinterpret_cast` to C-style, but you've expressed the conversion from the other direction. I'll try to clarify that in my answer. – Edward Aug 06 '14 at 17:30
  • So when you say non-portable you really mean that this code invokes undefined behavior but may have well defined implementation defined results. – Shafik Yaghmour Aug 06 '14 at 18:02
  • 1
    @ShafikYaghmour: If we understand that "undefined behavior" in this context means "undefined by the standard", then yes, that's a fair rephrasing. – Edward Aug 06 '14 at 18:57
  • @Edward I am curious, so do you have a `gcc` or `clang` reference that says that they support this behavior? This is basically offsetof hack which we can find evidence that it should work but debatable that it is actually supported. – Shafik Yaghmour Aug 06 '14 at 19:00
  • @ShafikYaghmour: That's a very reasonable question. I've updated my answer to attempt to address it. – Edward Aug 06 '14 at 19:47
  • to clarify I was referring to your use of `nullptr` here `&(B&)*(C*)nullptr`. – Shafik Yaghmour Aug 06 '14 at 19:53