7

I'm trying to write a struct to calculate the pointer offset between a base and a derived class as a constant expression in C++03. The code is as follows:

template <typename Base, typename Derived, typename Via = Derived>
struct OffsetToBase
{
    static const std::ptrdiff_t val =
        (const char*const)static_cast<Base*const>(static_cast<Via*const>((Derived*const)(1u << 7))) -
        (const char*const)(1u << 7);
};

The code compiles in GCC, but not in clang and VC. The error produced by clang and VC basically says that the initializer is not a constant expression with clang further underlines the sub-expression (Derived*const)(1u << 7).

So, my question is what the standards say about this? And if the initializer does not qualify as a constant expression according to the standards, then what is the reasoning behind this?

UPDATE: For your interest, I've found these two discussions:

"Initializer element is not constant" error for no reason in Linux GCC, compiling C

About cast in integer constant expression (in standard C)

But I don't think the same rules apply to C++.

Community
  • 1
  • 1
Lingxi
  • 14,579
  • 2
  • 37
  • 93

1 Answers1

1

This is not a constant expression, according to my reading of the standard:

5.19 Constant expressions

...

A conditional-expression is a core constant expression unless it involves one of the following

...

- a subtraction (5.7) where both operands are pointers;

Your expression subtracts one const char pointer from another. That seems to disqualify the whole thing from being considered as a constant expression.

In your case, both pointers in questions themselves are constant values, dummy values. If that's the case, then it is theoretically possible to figure out the result of their subtraction, as a constant expression, but it's not. Whatever.

I don't think you even need to use pointer subtraction, or that a static cast of a pointer would work here, you need to do a static cast on a reference. The following works for me, at least with gcc:

class B { int a; };
class C { int b; };

class A : public B, public C {};

int main()
{
    static const long n=(long)&static_cast<C &>(*(A *)0);
    return 0;
}
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Of course `static_cast` works on pointers. Just do the subtraction _after_ the conversion to integer. – Oktalist Dec 25 '14 at 13:24
  • Thanks for the quotation from the standard. However, it does not seem to solve the real problem, as the following code (casting to `const std::ptrdiff_t` instead of `const char*const`) does not compiler on clang and VC either: `template struct OffsetToBase { static const std::ptrdiff_t val = (const std::ptrdiff_t)static_cast(static_cast((Derived*const)(1u << 7))) - (const std::ptrdiff_t)(1u << 7); };` – Lingxi Dec 25 '14 at 13:48
  • It's UB only when you actually dereference a null pointer (that is, convert the lvalue to an rvalue). Additionally, using a null pointer to compute the offset of a class member is an ancient trick from the days of plain ol' C. – Sam Varshavchik Dec 25 '14 at 14:14
  • Did you try the example I gave, that does not involve pointer subtraction, with those other compilers? – Sam Varshavchik Dec 25 '14 at 14:15
  • @SamVarshavchik The example you gave does not meet my needs. What I need is a generic way to compute the offset. In Microsoft COM, this can then be used to implement IUnknown in a table-driven manner (see the book Essential COM, if you are interested in this). Also, I think dereferencing a pointer gives an lvalue rather than an rvalue. – Lingxi Dec 25 '14 at 14:29
  • @SamVarshavchik Your code does not compile either on VC and clang if `n` is made a static const member with an in-class initializer. – Lingxi Dec 25 '14 at 14:40
  • @SamVarshavchik: I don't know how things work in C, but this is most definitely UB in C++. – Andy Prowl Dec 28 '14 at 10:40