2

So, I have something along the lines of these structs:

struct Generic {}
struct Specific : Generic {}

At some point I have the the need to downcast, ie:

Specific s = (Specific) GetGenericData();

This is a problem because I get error messages stating that no user-defined cast was available.

I can change the code to be:

Specific s = (*(Specific *)&GetGenericData())

or using reinterpret_cast, it would be:

Specific s = *reinterpret_cast<Specific *>(&GetGenericData());

But, is there a way to make this cleaner? Perhaps using a macro or template?

I looked at this post C++ covariant templates, and I think it has some similarities, but not sure how to rewrite it for my case. I really don't want to define things as SmartPtr. I would rather keep things as the objects they are.

Community
  • 1
  • 1
user3072517
  • 513
  • 1
  • 7
  • 21

4 Answers4

7

It looks like GetGenericData() from your usage returns a Generic by-value, in which case a cast to Specific will be unsafe due to object slicing.

To do what you want to do, you should make it return a pointer or reference:

Generic* GetGenericData();
Generic& GetGenericDataRef();

And then you can perform a cast:

// safe, returns nullptr if it's not actually a Specific*
auto safe = dynamic_cast<Specific*>(GetGenericData());

// for references, this will throw std::bad_cast
// if you try the wrong type
auto& safe_ref = dynamic_cast<Specific&>(GetGenericDataRef());

// unsafe, undefined behavior if it's the wrong type,
// but faster if it is
auto unsafe = static_cast<Specific*>(GetGenericData());
Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • It should be noted that returning a reference requires the function to keep the object alive, and returning a raw pointer to freestore memory is usually a very bad idea. – Konrad Rudolph Jan 29 '15 at 20:51
  • @KonradRudolph ... or to just pointer/returning a reference to something exists already somewhere. – Barry Jan 29 '15 at 20:52
  • That’s what I mean with “requires the function to keep the object alive”. Anyway, this works well in some situations but not at all in others. – Konrad Rudolph Jan 29 '15 at 20:54
  • 3
    Note that `dynamic_cast` only works if `Generic` is polymorphic (which is not the OP's case as currently written, a virtual destructor can be added to fix that). – Jarod42 Jan 29 '15 at 21:02
  • So trying your example of dynamic_cast, I get: the operand of a runtime dynamic_cast must have a polymorphic class type @Barry, I am intrigued by your comments regarding "... or to just pointer/returning a reference to something exists already somewhere. How would that look in this case?" Would I pass the pointer into the GetGenericData function? – user3072517 Jan 29 '15 at 21:37
1

I assume here that your data is simple.

struct Generic {
  int x=0;
  int y=0;
};
struct Specific:Generic{
  int z=0;
  explicit Specific(Generic const&o):Generic(o){}

  // boilerplate, some may not be needed, but good habit:
  Specific()=default;
  Specific(Specific const&)=default;
  Specific(Specific &&)=default;
  Specific& operator=(Specific const&)=default;
  Specific& operator=(Specific &&)=default;
};

and bob is your uncle. It is somewhat important that int z hae a default initializer, so we don't have to repeat it in the from-parent ctor.

I made thr ctor explicit so it will be called only explicitly, instead of by accident.

This is a suitable solution for simple data.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • That is more what I was looking for. I kind of started down that path, but yours is much more well rounded! Thanks! – user3072517 Jan 30 '15 at 02:02
0

So the first step is to realize you have a dynamic state problem. The nature of the state you store changes based off dynamic information.

struct GenericState { virtual ~GenericState() {} }; // data in here
struct Generic;
template<class D>
struct GenericBase {
  D& self() { return *static_cast<D&>(*this); }
  D const& self() const { return *static_cast<D&>(*this); }
  // code to interact with GenericState here via self().pImpl
  // if you have `virtual` behavior, have a non-virtual method forward to
  // a `virtual` method in GenericState.
};

struct Generic:GenericBase<Generic> {
  // ctors go here, creates a GenericState in the pImpl below, or whatever
  ~GenericState() {} // not virtual
private:
  friend struct GenericBase<Generic>;
  std::unique_ptr<GenericState> pImpl;
};
struct SpecificState : GenericState {
  // specific stuff in here, including possible virtual method overrides
};

struct Specific : GenericBase<Specific> {
  // different ctors, creates a SpecificState in a pImpl
  // upcast operators:
  operator Generic() && { /* move pImpl into return value */ }
  operator Generic() const& { /* copy pImpl into return value */ }
private:
  friend struct GenericBase<Specific>;
  std::unique_ptr<SpecificState> pImpl;
};

If you want the ability to copy, implement a virtual GenericState* clone() const method in GenericState, and in SpecificState override it covariantly.

What I have done here is regularized the type (or semiregularized if we don't support move). The Specific and Generic types are unrelated, but their back end implementation details (GenericState and SpecificState) are related.

Interface duplication is avoided mostly via CRTP and GenericBase.

Downcasting now can either involve a dynamic check or not. You go through the pImpl and cast it over. If done in an rvalue context, it moves -- if in an lvalue context, it copies.

You could use shared pointers instead of unique pointers if you prefer. That would permit non-copy non-move based casting.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Holy cow! I see what you are doing, but I just don't get why does this has to be so bleeping complicated from a coding perspective. These are structs not classes, so I would think there would be a little bit more intelligence in the compiler when dealing with a struct. Yes, structs are intrinsically classes, but still. To do this for each and every one of my classes (and there are hundreds), is ridiculous. I guess I am going to have to revert back to a copy constructor. But again, seems like this should be simpler. – user3072517 Jan 29 '15 at 23:18
  • @user307 I assumed they where more than just dumb data. And felt kike dumping a manual object system. If just dumb structs, the above is way overkill. – Yakk - Adam Nevraumont Jan 29 '15 at 23:51
  • @Yakk....don't get me wrong, your answer is very interesting (and I might even use the idea it on some classes, so thanks for that!)....But, what I am troubled by, is that I just don't know why in C++ casting of dumb struct objects has to be so complicated. There has to be a simpler way. – user3072517 Jan 29 '15 at 23:58
  • @user307 `struct General{int x;}; struct Specific:General{int y;};` When you convert, where does `y`'s value come from? `Specific`->`General` `y` is discarded. `General`->`Specific` `y` must come from somewhere. – Yakk - Adam Nevraumont Jan 30 '15 at 00:14
  • I get it....but for the objects which are in common, I would think there would be an automatic way to do this. For the ones which Specific adds, then the struct initializer should take over. Just seems to me that there should be a way to do this without so much development effort. Ok...I am whining now....thanks for your suggestions. I am going to do the copy constructor. – user3072517 Jan 30 '15 at 00:31
  • @user no do not write the above. Easy solution incoming – Yakk - Adam Nevraumont Jan 30 '15 at 01:35
0

Ok, after some additional study, I am wondering if what is wrong with doing this:

struct Generic {}
struct Specific : Generic {
    Specific( const Generic &obj ) : Generic(obj) {}
}

Correct me if I am wrong, but this works using the implicit copy constructors.

Assuming that is the case, I can avoid having to write one and does perform the casting automatically, and I can now write:

Specific s = GetGenericData();

Granted, for large objects, this is probably not a good idea, but for smaller ones, will this be a "correct" solution?

user3072517
  • 513
  • 1
  • 7
  • 21