Broothy
Broothy

Reputation: 721

Base to Derived static_cast, not visible relation

We can static_cast from Base* to Derived*, but in my case the relationship is not visible when the cast is supposed to happen. Is there any trick (besides of reinterpret_cast of course) to make it work?

struct Derived;

struct Base 
{
    Base()
    {
        //Is there a way to make it work?
        //Derived* d = static_cast<Derived*>(this);
    }
};

struct Derived: public Base
{
};

int main()
{
    Base b{};
    // Here it works as the compiler is aware of the relationship
    Derived* d = static_cast<Derived*>(&b);
}

You can try it here.

Update: Thanks for your comments. Of course, I know that my example code is broken, I just wanted to demonstrate the problem. The real world problem is much more complex.

I have an opaque pointer that points to either Base or Derived (compile time switch). And I need that opaque pointer in the constructor of the Base class. When the opaque pointer is Derived* then Base cannot be instantiated just through Derived, so it is 100% safe. In the other case Base == Derived so no cast would happen.

As it is an opaque pointer and we are in Base, we have only a forward declaration of Derived. Like:

#ifdef PLATFORM_A
    typedef struct Derived UsedType;
#else
    typedef struct Base UsedType;
#endif

So I would need an UsedType* in the Base constructor, the virtual this is either Base* or Derived*. Unfortunately if we are not on PLATFORM_A then Derived is not defined at all, so when the code reaches Base's constructor the compiler cannot know about it's relation with Derived (as Derived might not exist at all, and in that case the cast is from Base* to Base*).

Update 2: Real world code

Update 3: I updated the code once more. Now there is a member that stores the opaque this pointer. I don't use it in the constructor, I use it only outside. It needs to be initialized in the constructor (as the member is const).

#include <iostream>

#define PLATFORM_A

#ifdef PLATFORM_A
    typedef struct Derived UsedType;
#else
    typedef struct Base UsedType;
#endif

struct User 
{
    UsedType* const platform;
    void print() const;
};

struct Base 
{
    const char* const name{"Base"};
    User const iNeedThis;
#ifdef PLATFORM_A
  protected:
#endif
    Base()
      : iNeedThis{(UsedType*)this}
      //: iNeedThis{static_cast<UsedType*>(this)}
    {
    }
};

#ifdef PLATFORM_A
  struct Derived: public Base
  {
    const char* const name{"Derived"};
    Derived()
      : Base()
    {
    }
  };
#endif

void User::print() const
{
    std::cout << platform->name << std::endl;
}

int main()
{
    UsedType myObj{};
    myObj.iNeedThis.print();
}

https://godbolt.org/z/sMr5Kfxrn

Upvotes: 0

Views: 101

Answers (2)

Useless
Useless

Reputation: 67802

I mean, it's trivial to do literally what you asked, even without moving the constructor:

struct Base 
{
  UsedType* self();

#ifdef PLATFORM_A
  protected:
#endif
    Base()
    {
        UsedType* iNeedThis = self();
    }
};

Later on, in base.cpp or wherever:

#ifndef PLATFORM_A
  UsedType* Base::self() { return this; }
#endif

and even later on, in derived.cpp

#ifdef PLATFORM_A
UsedType* Base::self()
{
  return static_cast<Derived*>(this);
}
#endif

But it still seems like a bad design.

Upvotes: 2

Remy Lebeau
Remy Lebeau

Reputation: 597906

To answer your question - you can't cast to an incomplete type, so you would need to move the implementation of Base() after the declaration of Derived, eg:

struct Derived;

struct Base 
{
    Base();
};

struct Derived: public Base
{
};

Base::Base()
{
    // see note below!!!
    Derived* d = static_cast<Derived*>(this);
}

int main()
{
    Base b{};
    // see note below!!!
    Derived* d = static_cast<Derived*>(&b);
}

That being said, what you are doing is risky, because this inside of the Base constructor is not pointing at a Derived object, and b inside of main() is not a Derived object either. If you try to dereference the casted pointer in either situation, you will invoke undefined behavior.

There is no way to make the cast inside of the Base constructor be legal, since any Derived object would simply not exist yet while the Base class is being constructed.

But, in main(), you can change b to be a pointer to a Derived object, then you can safely cast that pointer, eg:

int main()
{
    Derived obj{};
    Base* b = &obj;

    // this is perfectly OK!
    Derived* d = static_cast<Derived*>(b);
}

Upvotes: 4

Related Questions