10

Let's say I have these types:

struct A {
    int a;
};

struct B {
    int b;
};

struct C : public A, public B {
    int c;
};

A C* pointer can be cast to A* pointer without adjusting the actual address at all. But when C* is cast to B*, the value must change. I'd like to ensure that two related types I have can be cast to each other without a change in address (i.e. that there is no multiple inheritance, or that the base class is the first base of the derived class). This could be checked at run-time, e.g. like so

assert(size_t(static_cast<A*>((C*)0xF000) == 0xF000);
assert(size_t(static_cast<B*>((C*)0xF000) != 0xF000);

That works. But this information is known at compile time, so I'm looking for a way to do a compile-time assert on it. The obvious ways of converting the above to a static assert (e.g. replace assert with BOOST_STATIC_ASSERT give the error "a cast to a type other than an integral or enumeration type cannot appear in a constant-expression" with g++ 4.2.

Portability isn't too important. Using gcc extensions, or hacky template tricks would all be fine.

Update: Found that almost the same question has been asked before: C++, statically detect base classes with differing addresses?. Using offsetof() is the only useful suggestion there too.

Community
  • 1
  • 1
DS.
  • 22,632
  • 6
  • 47
  • 54
  • 8
    Not wanting to be a nitpick, but since the exact memory layout is an implementation detail... why would you be interested in this exactly ? – Matthieu M. Apr 04 '11 at 06:23
  • I'm pretty sure that the Boost/lambda libraries contain stuff for this. I'd have to look it up but Alexandrescu and Vandervoorde have published just about every trick in this field – sehe Apr 04 '11 at 06:39
  • I am really interested in the question that @Matthieu M. presents: Under what circumstance does it matter if a cast can be trivial? (i.e. if it is not trivial, the cost is most probably negligible: addition of a constant to the derived pointer, and if the condition is not properly done you will run into real pain trying to debug what will look like corrupted memory) – David Rodríguez - dribeas Apr 04 '11 at 07:47
  • It's a long story. It's for an event system, where I'd like to use type erasure to reduce template bloat. E.g. I'd like to treat function pointers of type f(T*) (for many different T) as f(void*), to store them in common structures. Further details are complicated. The point is that there is a lot of duplicated templated code which is actually identical because the cast is trivial (in most cases I'm dealing with, it actually is). I'm trying to avoid the duplication of identical generated code, but have the compiler check that I am not messing up :) – DS. Apr 04 '11 at 15:05
  • @sehe: I looked, and the closest I found was boost::is_convertible(), which isn't quite it. – DS. Apr 04 '11 at 15:18
  • 2
    @DS, you might wish to check your linker's settings. Some linkers have the ability to detect and merge identical functions, so even though your compiler is generating lots of duplicates, the work *you're* doing to merge them may be work that the computer can do for you instead. – Rob Kennedy Apr 04 '11 at 18:16
  • And to add to Rob's response, the linker may also merge all +4 adjustor thunks. You wouldn't catch those in any case. – MSalters Apr 05 '11 at 07:06

2 Answers2

5

"I'd like to ensure that two related types can be cast to each other without a change in address (i.e. that there is no multiple inheritance, or that the base class is the first base of the derived class)."

Your "i.e." isn't correct. For instance, it's entirely possible that the first 4 bytes of Derived are a vtable pointer, even when Base isn't polymorphic. Yes, C++ compilers are easier if (1) the first base subobject is at offset 0, and (2) the vtable pointer is at offset 0. But those two goals are inherently at odds, and there is no clear better choice.

Now, the first part could be tested in theory. With standard-layout types, there would be no difference in offset_of(Base, first_member) and offset_of(Derived, first_member). But in practice, offset_of doesn't work for interesting types; it's UB. And the whole point of this check is to check a type, so it should fail reliably for nonstandard-layout types.

Quuxplusone
  • 23,928
  • 8
  • 94
  • 159
MSalters
  • 173,980
  • 10
  • 155
  • 350
  • 1
    "You can't check that, because it's not true". What I meant is that since it's not always true, I need to be able to check! Now, the offsetof(C,a) idea sounded very promising, but gcc complains about "Invalid access to non-static data member 'A::a' of NULL object." – DS. Apr 04 '11 at 15:14
  • Actually, the gcc complaint is a warning, not an error. So this does work when the first member of Base is known. That's something. Thanks! – DS. Apr 04 '11 at 15:33
  • I've edited MSalters' comment "You can't check that, because it's not true" to the I-think-clearer "Your 'i.e.' isn't correct". I don't think MSalters could have been trying to say that your whole statement was wrong. Based on his next sentence, I think he just meant that "two related types can be cast..." is not in fact truly equivalent to "there is no multiple inheritance...". – Quuxplusone Dec 19 '13 at 19:07
2

Based on a suggestion from MSalters, and an answer from C++, statically detect base classes with differing addresses?, here is the closest thing to an answer I can come up with. It's probably gcc-specific, and requires knowing some member of the base class:

#pragma GCC diagnostic ignored "-Winvalid-offsetof"     // To suppress warning.
BOOST_STATIC_ASSERT(offsetof(C, a) == offsetof(A, a));
BOOST_STATIC_ASSERT(offsetof(C, b) != offsetof(B, b));
#pragma GCC diagnostic warn "-Winvalid-offsetof"

Obviously this is both inconvenient and scary (requires to know a member and to turn off a warning).

Community
  • 1
  • 1
DS.
  • 22,632
  • 6
  • 47
  • 54