4

I've got some code that uses a somewhat sneaky cast from a base class type to a child class type, where the child class type is specified as a template parameter. I'm assuming since the base class declares no data members and is zero-sized, the base class pointer address will be the same as the child, and the cast will succeed. So far the code runs correctly.

Here's a simplified version of what I'm doing:

template <class RangeT>
struct CppIterator {
    CppIterator(const RangeT& range) { ... }
    // ... methods calling RangeT's members
};

// Base class, provides begin() / end() methods. 
template<class RangeT>
struct CppIterableBase {
    CppIterator<RangeT> begin() { 
        return CppIterator<RangeT>( *(RangeT*)this );  // Is this safe?
    }
    CppIterator<RangeT> end() { ... }
};

struct MyRange : CppIterableBase<MyRange> {
    // ... 
};

My question is basically - is the code kosher? Will a base pointer always equate to a child pointer if the base is empty? Is it implementation-dependent? Will I run into trouble later on?

It solves my problem nicely but I'm a little dubious.

Cœur
  • 37,241
  • 25
  • 195
  • 267
QuadrupleA
  • 876
  • 6
  • 23
  • Why do you (think you) need this? – Mats Petersson Mar 08 '15 at 18:41
  • I have a variety of range types - inheriting prevents me from needing to define the same begin() and end() methods over and over again. – QuadrupleA Mar 08 '15 at 18:44
  • I meant why you need to cast it, not why you need an iterator class with template type. – Mats Petersson Mar 08 '15 at 18:45
  • 3
    The name for this pattern is CRTP. Use a `static_cast`, not the C-style cast, which can degenerate into a dangerous `reinterpret_cast` if you mess up the inheritance. – T.C. Mar 08 '15 at 18:46
  • @MatsPetersson CppIterator accesses members in MyRange, not CppIterableBase, so I think the type passed into its constructor needs to be MyRange (compilation fails without the cast, although the error messages are crpytic). – QuadrupleA Mar 08 '15 at 18:51
  • @T.C. Thanks - looks like it's indeed the CRTP - I've run into it with Boost and metaprogramming a bit without reading up on it further. Good to know it's an established pattern. static_cast worked here, though I think I need to research a bit to understand how it's different. – QuadrupleA Mar 08 '15 at 19:11
  • 1
    note: you can write `static_cast(*this)` , rather than casting to pointer and dereferencing – M.M Mar 08 '15 at 19:52

2 Answers2

2

There's no need for the base class to be empty.

As long as the base is accessible, unambiguous, and non-virtual, and the pointer-to-base actually points to the base subobject of a derived object, it is valid to perform a static_cast from a pointer-to-base to pointer-to-derived. The compiler will perform any adjustment necessary to the pointer value.

What you are doing is actually a common pattern, called the curiously recurring template pattern (or CRTP).

reinterpret_cast is a completely different beast, however, and in this case would probably be a one-way ticket to undefined behavior land (I'm too lazy to dig into the standard and figure out under which corner cases it isn't UB). And since a C-style cast can become a reinterpret_cast (here, if you accidentally derive Foo from Base<Baz> when Baz doesn't derive from Base<Baz>), it should not be used.

Additionally, there are ways to ensure that you don't accidentally derive Foo from Base<Bar> - which simply using static_cast will not prevent if Bar also derives from Base<Bar>.

Community
  • 1
  • 1
T.C.
  • 133,968
  • 17
  • 288
  • 421
2

This is the CRTP.

Using static_cast<Derived*>(this) is safer, as C-style casts will in some cases be "too strong".

Using Foo<> for thr CRTP base class:

So long as the Foo<Derived> in question is actually a base of Derived, then a static_cast<Derived*>(this) is safe at runtime and does the right thing.

A static_assert of is_base_of can reduce one source of possible error here, but not all. If struct Bar : Foo<Derived> the above cast does undefined behaviour, and I know of no way to check for that error.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • You can't check at the point of the `static_cast`, but it is possible to ensure that creating a `Bar` object doesn't compile (for some definition of "ensure"). – T.C. Mar 08 '15 at 19:35