Why typecasting an integer literal to a pointer value results in a non-const expression?
Asked Answered
V

1

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++.

Vehement answered 25/12, 2014 at 10:37 Comment(0)
R
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;
}
Rancidity answered 25/12, 2014 at 12:25 Comment(7)
Of course static_cast works on pointers. Just do the subtraction after the conversion to integer.Villainous
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 <typename Base, typename Derived, typename Via = Derived> struct OffsetToBase { static const std::ptrdiff_t val = (const std::ptrdiff_t)static_cast<Base*const>(static_cast<Via*const>((Derived*const)(1u << 7))) - (const std::ptrdiff_t)(1u << 7); };Vehement
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.Rancidity
Did you try the example I gave, that does not involve pointer subtraction, with those other compilers?Rancidity
@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.Vehement
@SamVarshavchik Your code does not compile either on VC and clang if n is made a static const member with an in-class initializer.Vehement
@SamVarshavchik: I don't know how things work in C, but this is most definitely UB in C++.Dervish

© 2022 - 2024 — McMap. All rights reserved.