I'm working on a C++ program depends on a C library that uses the "struct embedding" trick to simulate inheritance (of data storage, not behaviour), similar to the Parent and Child structs in the example below.
When I have a pointer to one of the children (e.g. ChildA *), I can't pass it to a function that expects a Parent * without an explicit cast.
With regular C++ inheritance, such an up-cast is unnecessary/implied. Is there a way to make it work similarly with this C pseudo-inheritence?
- If that's possible, is it even advisable?
- And if not, is that
&child->basealternative preferable over the cast?
Here's a concrete example:
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::vector;
struct Parent {
int id;
};
struct ChildA {
struct Parent base; // Simulates inheritance
int a;
};
struct ChildB {
struct Parent base;
int b;
};
struct ChildC {
struct Parent base;
int c;
};
int main(int argc, char *argv[]) {
// Example objects of different types
struct ChildA a = { .base = { .id = 1 }, .a = 1 };
struct ChildB b = { .base = { .id = 2 }, .b = 2 };
struct ChildC c = { .base = { .id = 3 }, .c = 3 };
struct ChildA *pointer_to_a = &a;
struct ChildB *pointer_to_b = &b;
struct ChildC *pointer_to_c = &c;
// Heterogeneous objects can be psuedo up-casted and held in a container
vector<struct Parent *> v{
// Can this up-cast be made implicit, like it is for regular C++ classes?
reinterpret_cast<struct Parent *>(pointer_to_a),
reinterpret_cast<struct Parent *>(pointer_to_b),
reinterpret_cast<struct Parent *>(pointer_to_c),
};
// Is this better than the cast?
vector<struct Parent *> v2{
&pointer_to_a->base,
&pointer_to_b->base,
&pointer_to_c->base,
};
// Example of homogeneous usage of the common "base"
// parts of the various objects
for (auto &p : v) {
cout << p->id << endl;
}
}
2 Answers 2
As far as C++ is concerned, ChildA is not at all related to Parent, so the only cast that converts between ChildA* and Parent* is a reinterpret_cast.
However, you can implement auto ChildA::operator Parent*() { return &base; } (or C++23 template <typename THIS> auto ChildA::operator Parent*(THIS&& this) { return &this->base; }). This works because unlike ChildA*, ChildA itself is a user-defined-type with user-defined conversion operators.
BTW, vector<struct Parent *> makes no sense. std::vector<> is C++-only, and then the struct is redundant.
3 Comments
I guess you won't like it but, could class "wrappers" / kind of duplicates be an option?
Something like
class P_class{
public:
struct Parent base;
P_class(struct Parent p):base(p){};
};
class A_class : public P_class{
public:
int *a;
A_class(struct ChildA *c_a) : P_class(c_a->base),a(&(c_a->a)){};
};
//...
// then you can use classes (maybe directly from a, b & c)
A_class points_to_a(pointer_to_a);
B_class points_to_b(pointer_to_b);
C_class points_to_c(pointer_to_c);
// ...
// BTW, I guess v2 only had the Parent contents...
vector<P_class*> v3{
&points_to_a, &points_to_b, &points_to_c
};
?
Considering your comment, you don't mind having a couple of pointers per child (object), so having an intermediate (pointers) class is OK
But you "have over 150 child types."
So, maybe one wrapper to rule them all children could do? Something like :
class P_class{
public:
struct Parent *base;
P_class(struct Parent *p):base(p){cout<<"Impl P with "<<base->id<<endl;};
int id(){return this->base->id;};
};
template <typename sub_struct>
class Children_s : public P_class{
public:
sub_struct *obj;// the child struct
Children_s(sub_struct* ss):obj(ss), P_class(&ss->base){};
};
int main(int argc, char *argv[]) {
// Example objects of different types
struct ChildA a = { .base = { .id = 1 }, .a = 6 };
struct ChildB b = { .base = { .id = 2 }, .b = 7 };
struct ChildC c = { .base = { .id = 3 }, .c = 8 };
struct ChildA *pointer_to_a = &a;
struct ChildB *pointer_to_b = &b;
struct ChildC *pointer_to_c = &c;
Children_s<ChildA>* obj_a = new Children_s<ChildA>(&a);
Children_s<ChildB> obj_b (&b);
Children_s<ChildC>* obj_c = new Children_s<ChildC>(&c);
vector<P_class*> v3{obj_a, &obj_b};
v3.push_back(obj_c ) ;
for(auto &p: v3){
cout<<p->id() /* p->base->id */<<endl;
}
// vector<shared_ptr<P_class>> v4 {shared_ptr<P_class>(obj_a),shared_ptr<P_class>(&obj_b)};
&pointer_to_a->basevariant?