-
Notifications
You must be signed in to change notification settings - Fork 5
Allow polymorphism with fine::ResourcePtr
#8
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
Open
brodeuralexis
wants to merge
2
commits into
elixir-nx:main
Choose a base branch
from
brodeuralexis:feature/polymorphic-resources
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we have a
make_polymorphic_resource<Base, Concrete>(...)
function instead, and havemake_resource<T>(...)
as a alias formake_polymorphic_resource<T, T>(...)
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @brodeuralexis!
I think if we want to make it polymorphic, ideally we should keep it close the the std smart pointers (and regular pointers), so similarly to:
we would have:
My understanding is that we would need a constructor with
std::is_convertible_v<U*, T*>
check. The issue is thatfine::ResourcePtr<T>
does not storeT*
directly, it storesfine::ResourceWrapper<T>*
.Perhaps we could kill
ResourceWrapper
if we manage the memory alignment without that struct. Sofine::ResourcePtr<T>
would storeT*
, then when allocating the resource we would allocate memory space for bothinitialized
flag andT
.I was trying to avoid messing with alignment manually, but you do it in the PR also, so perhaps there is no way to avoid it either way.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gah, I guess we need to know
AbstractRes
upfront, because it's the one withFINE_RESOURCE
.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, we allow for defining optional callbacks on the resource class, currently we only have one:
void destructor(ErlNifEnv *env)
.So if they define it on
ConcreteRes
and do not define as virtual method onAbstractRes
, it wouldn't be called. So it could also be a footgun.Do you have a use case where you actually needed a polymorphic resource like this? For example, I think you could do this:
This way we move the polymorphism one level down.
I actually think it's good to keep the resource structs simple.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My goal with this feature was to remove the need for an allocation using
std::unique_ptr
.We could also argue that the same footgun will happen if
~Abstract
is not virtual.We could introduce
fine::Resource
with a virtualdestructor(ErlNifEnv*)
method, and requirefine::Registration::register_resource
forT
wherestd::is_base_of_v<Resource, T>
. This would remove the need forhas_destructor
, since now all resources will have thedestructor
method and provide a place to add callbacks if needed with potential default behaviour.Although this will prevent registering NIF resources on classes the NIF code doesn't control, we could introduce a separate
register_resource
to deal with such a case.enif_alloc
ensures that memory is aligned foralignof(max_align_t)
, but I must also ensure thatT
starts correctly aligned atalignof(max_align_t)
even ifalignof(T) <= alignof(max_align_t)
. At the end, the current approach minimizes the use of pointer arithmetic at the cost of using a union.This is a feature I believe will make writing more convenient, as it would encourage NIF resources to be more than data containers. I would also be more in line with
shared_ptr
.I stumbled upon the, I need the base resource type for the ErlNifResourceType*`, problem too. I would have to do some tests to see if there is a way to recursively traverse base classes using template metaprogramming.
Otherwise, we could introduce
fine::ResourcePtr<T>::make_polymorphic<U, Args...>(Args&&...)
, and havefine::make_resource<T, Args..>(Args&&...)
stay the same. This would make it even more explicit (and more of an advanced API), as well as make the base and concrete templated types more distinguishable.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As an aside, I have the feeling that nifpp was also trying to get this to work:
https://github.com/saleyn/nifpp/blob/main/nifpp.h#L747-L751
But while they have the dynamic cast working, their resource creation is not polymorphic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did that initially, but I ended up no liking it, because it limits resources only to user-defined classes. And yes, we could have both, but I really think we should have only one abstraction.
It's likely doable, though the problem is that someone may also use a derived class as a resource (they may not even know it's derived) and in that case we don't want to find the base one.
I don't like the divergence from std pointers, but yeah, passing both classes explicitly may be the only reasonable way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I've had no luck with trying to get base classes through templates without some kind of reflection.
If we include this, I would go for a
fine::ResourcePtr<Base>::make_polymorphic<Concrete, Args...>(Args&&...)
function. This makes the base and concrete type easily identifiable.fine::make_resource<T, Args...>(Args&&...)
's behaviour would be left unchanged.This would obviously be a pretty advanced/complex feature for fine. While I believe its inclusion to be worthwhile, I will defer to your opinion, since, as you correctly pointed out, a layer of indirection can always be added through a
std::unique_ptr
inside the resource to solve the problem at the cost of an extra allocation.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I would rather not expand the API for this purpose, so for now I would not do it. Thank you for discussing this through!