0

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->base alternative 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;
 }
}
asked Aug 13, 2024 at 1:53
12
  • No, it's not advisable. Why don't you just use inheritance instead of encapsulation? Commented Aug 13, 2024 at 1:55
  • @TimRoberts Ah I omitted a key detail: the library in question is written in C Commented Aug 13, 2024 at 1:57
  • 1
    No, it is not advisable, unless you enjoy dealing with implications of undefined behaviour. If your code is intended to be used from C++, just use inheritance that is part of the language. If your code is intended to be used from C, instead of using casts, access the named "embedded" member. Commented Aug 13, 2024 at 1:58
  • @Peter Thanks for the input. I don't think this is UB in C (source), is this UB in C++? And by 'access the named "embedded" member"', are you revering to the &pointer_to_a->base variant? Commented Aug 13, 2024 at 2:02
  • It is undefined behaviour in C++ (although I suppose language lawyers might mention specific circumstances where it isn't). That link makes some very specific assumptions - which, if not met, would also introduce undefined behaviour in C. Potential for UB aside, explicit type conversions are easy to misuse, so better avoided, and in this case, you're achieving nothing more than accessing the named member. If the class is maintained (e.g. to add other members to the "child" classes) it is also easy to introduce UB in code that uses the cast, which using a named member avoids. Commented Aug 13, 2024 at 2:26

2 Answers 2

1

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.

answered Aug 13, 2024 at 15:31
Sign up to request clarification or add additional context in comments.

3 Comments

"However, you can implement" Do you think doing so would be advisable? Up-side: marginally cleaner code. Downside: hidden casts, which although they should always succeed, are now implicit and harder to discover
@Alexander: Advisable would be to wrap the entire C library in a C++ interface. The compatibility with C is intended for this use; not to mix the two at a fine-grained level.
Funny enough, that's kind of what I'm doing, by translating this new parser API into the AST node types of the parser it's replacing (which are regular C++ classes). I'm encountering these casts in the process of implementing that translation layer.
1

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)};
answered Aug 13, 2024 at 15:13

2 Comments

This is a good answer for when there's only a few cases, but unfortunately, I have over 150 child types. I could code-gen the wrappers, but they're going to be quite unwieldy
Updated the answer with a tempale based wrapper then (only 1 needed for your >150 children)

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.