Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add constructor for box<T> from box<Y> for compatible Y #157

Open
KholdStare opened this issue Aug 25, 2020 · 3 comments
Open

Add constructor for box<T> from box<Y> for compatible Y #157

KholdStare opened this issue Aug 25, 2020 · 3 comments

Comments

@KholdStare
Copy link

Similar to how https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr has a constructor (9) that participates in overload resolution if Y* is compatible with T*, that can work for box<T>.

E.g. something like:

struct A
{
  int x;
};

struct B : A
{
  int y;
};

void example()
{
  immer::box<B> b;
  immer::box<A> a{b}; // should work
}

I don't think there should be concerns with object slicing since the values held are immutable and are not copied/moved by immer. I think implementation-wise this would mean a virtual function or some function pointer in the MemoryPolicy::refcount class that does deletion. What do you think?

@arximboldi
Copy link
Owner

arximboldi commented Aug 25, 2020

Hmmm, I find the suggestion interesting, but I'm not sure supporting sharing in this case (instead of slicing) justifies the added complexity. In any case due to immutability the two cases (slicing or sharing) should be semantically equivalent.

If we go down this road, note that you are most probably using inheritance for composition. One could also consider this other use-case, which is actually supported by shared ptr as well:

struct A { ... };

struct B 
{
    A member;
};

void example()
{
    immer::box<B> b;
    immer::box<A> a{b.member, b};
}

@KholdStare
Copy link
Author

KholdStare commented Aug 25, 2020

I see what you mean. I'm going to give an extremely contrived example of my usecase:

struct Product
{
  int id;
};

struct Hat : Product
{
  Color color;
};

struct CustomerOrder
{
  immer::box<Product> product;
};

We're writing a HatStore and we know all our products are going to be Hats. In order to avoid excessive templates, I'm keeping CustomerOrder as is, and don't want to have CustomerOrder<Hat>. Some component will want to look at box::immer<Hat> at some point so there will need to be some downcasting involved from Product to Hat.

Without downcasting, sharing and slicing would be semantically equivalent, I agree. Here I was trying to avoid putting more templates than necessary, and that involves "knowledge outside the type system" that all Products happen to be Hats for this particular application.

@arximboldi
Copy link
Owner

Hi!

Sorry for the late reply, I was on vacation :)

I see, that is a bit of an unorthodox design, I am unsure yet what the full consequences of that would be (How do you discriminate the types for downcasting? Is there a place where you can query any product? etc.).

Normally you would just store the id in the customer order, and somewhere else in your program you have a immer::map<id, Hat> for hats that you pass around when you need to use hats. (Alternativelly, you can use Product = variant<Hat, ...> and put that in the map).

If this is for a commercial project, send me an email, we can maybe arrange a contract to develop this feature in the library, but maybe I can help you review the design to avoid these pitfalls.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants