#pragma once #include #include #if 0 //XXX: This doesn't work, we need `opaque_handle` to not be a template... namespace _opaque_impl { struct opaque_handle { virtual constexpr ~opaque_handle(); protected: inline constexpr explicit opaque_handle(void* data) noexcept : _ptr(data){} template inline constexpr static opaque_handle from_object(T& ptr) noexcept { return opaque_handle{dynamic_cast(&ptr) ?: static_cast(&ptr)}; } inline constexpr void* ptr() const noexcept { return _ptr; } template inline constexpr T* downcast() const noexcept { return dynamic_cast(_ptr); } template inline constexpr downcast_unsafe() const noexcept { return static_cast(_ptr); } template inline T* ptr_as() { return std::bit_cast(_ptr); } private: void* _ptr; }; template struct opaque_handle_for : opaque_handle { constexpr opaque_handle_for(T&& value) : opaque_handle(opaque_handle::template from_object(*new T(std::move(value)))){} constexpr virtual ~opaque_handle_for() { if(T* ptr = downcast_unsafe()) { delete ptr; } } } } struct opaque_handle { private: }; #endif struct opaque_not_copyable : std::exception{}; struct opaque_handle_moved : std::exception{}; struct opaque_bad_cast : std::exception{}; struct opaque_handle; struct opaque_handle_impl; struct opaque_handle_impl { friend class opaque_handle; virtual constexpr ~opaque_handle_impl() noexcept {} template inline constexpr const To* try_cast() const noexcept { return static_cast(as_raw_ptr()); } template inline constexpr To* try_cast() noexcept { return static_cast(as_raw_ptr()); } protected: constexpr virtual void* as_raw_ptr() const noexcept = 0; constexpr virtual opaque_handle_impl* try_clone() const = 0; inline virtual opaque_handle_impl& clone() const { if(auto* np = try_clone()) return *np; throw opaque_not_copyable{}; } constexpr opaque_handle_impl() noexcept{} }; struct opaque_handle final { constexpr explicit opaque_handle(opaque_handle_impl* ptr) noexcept : _impl(ptr){} constexpr opaque_handle(opaque_handle&& move) noexcept : _impl(std::exchange(move._impl, nullptr)){} inline opaque_handle(const opaque_handle& copy) : _impl(copy._impl ? std::addressof(copy._impl->clone()) //(copy._impl->try_clone() ?: throw opaque_not_copyable{}) : nullptr){} constexpr ~opaque_handle() { if(_impl) delete _impl; } constexpr const void* as_ptr() const noexcept { return _impl ? _impl->as_raw_ptr() : nullptr; } constexpr void* as_ptr() noexcept { return _impl ? _impl->as_raw_ptr() : nullptr; } template constexpr const T* try_cast() const noexcept { return static_cast(as_ptr()); } template constexpr T* try_cast() noexcept { return static_cast(as_ptr()); } constexpr bool has_value() const noexcept { return bool(_impl); } constexpr explicit operator bool() const noexcept { return has_value(); } template constexpr const T* operator->() const noexcept { return try_cast(); } template constexpr T* operator->() noexcept { return try_cast(); } //XXX: TODO: throw if try_cast() is null and T is not `void`. inline auto operator*() const noexcept { return _impl_deref_const{*this}; } inline auto operator*() noexcept { return _impl_deref{*this}; } /* template inline auto operator*() noexcept { return *(try_cast() ?: throw opaque_bad_cast{}); } */ template constexpr operator T*() noexcept { return try_cast(); } template constexpr operator const T*() const noexcept { return try_cast(); } private: struct _impl_deref_const { const opaque_handle& ptr; template inline operator const T&() && { return *(ptr.try_cast() ?: throw opaque_bad_cast{}); } template inline operator const T&() const&& { return *(ptr.try_cast() ?: throw opaque_bad_cast{}); } }; struct _impl_deref { opaque_handle& ptr; template inline operator T&() && { return *(ptr.try_cast() ?: throw opaque_bad_cast{}); } template inline operator const T&() const&& { return *(ptr.try_cast() ?: throw opaque_bad_cast{}); } }; opaque_handle_impl* _impl; }; template struct opaque_handle_object : public opaque_handle_impl { friend class opaque_handle; inline constexpr opaque_handle_object(T&& value) noexcept(std::is_nothrow_move_constructible_v) : _obj(std::move(value)){} inline constexpr virtual ~opaque_handle_object() {} protected: inline constexpr void* as_raw_ptr() const noexcept override { return const_cast(static_cast(&_obj) /*?: (static_cast(&_obj) ?: &_obj)*/); } inline constexpr opaque_handle_impl* try_clone() const override { if constexpr(std::is_copy_constructible_v) { return new opaque_handle_object{T{_obj}}; } else return nullptr; } private: T _obj; }; template opaque_handle_object(T&&) -> opaque_handle_object; /// Make an opaque_handle from an object. template constexpr inline opaque_handle make_opaque_object_handle(T&& value) noexcept(std::is_nothrow_move_constructible_v) { return opaque_handle{static_cast(new opaque_handle_object(std::forward(value)))}; } enum class opaque_handle_operation { Clone, Delete, }; template concept OpaqueHandleFunc = std::is_invocable_v && std::is_convertible_v, T*>; /// Create and opaque_handle from a data pointer and a lambda which handles the copying (if possible) and deleting of the object /// /// `handler` should be in the form: `auto* (T*, opaque_handle_operation) [noexcept]` and the reference must outlive the returned object. It should handle a `null` argument, and it can return `null`. template HandleF> // Note: This should be a lambda, or a free-function. it's lifetime must not end before the `opaque_handle` returned from this function does. constexpr inline opaque_handle make_opaque_handle(T* data, const HandleF& handler) noexcept { constexpr const bool is_nothrow = std::is_nothrow_invocable_v; struct object_handler { constexpr object_handler(const object_handler& c) noexcept(is_nothrow) : data(c.handler(data, opaque_handle_operation::Clone)) , handler(c.handler){} constexpr object_handler(object_handler&& m) noexcept : data(std::exchange(m.data, nullptr)) , handler(m.handler){} constexpr object_handler(T* data, const HandleF& handler) noexcept : data(data) , handler(handler){} constexpr /*virtual*/ ~object_handler() noexcept(is_nothrow) { if(data) handler(data, opaque_handle_operation::Delete); } T* data; const HandleF& handler; }; return make_opaque_object_handle(object_handler{data, handler}); } /// Create and opaque_handle from a data pointer and a move-constructible functor which handles the copying (if possible) and deleting of the object. /// /// `handler` should be in the form: `auto* (T*, opaque_handle_operation) [noexcept]`. Its lifetime is managed by the returned opaque_handle template HandleF> constexpr inline opaque_handle make_opaque_handle(T* data, HandleF&& handler) noexcept(std::is_nothrow_move_constructible_v) { constexpr const bool is_nothrow = std::is_nothrow_invocable_v; constexpr const bool is_nothrow_ctor = std::is_nothrow_move_constructible_v; struct object_handler { constexpr object_handler(const object_handler& c) noexcept(is_nothrow && std::is_nothrow_copy_constructible_v) requires(std::is_copy_constructible_v) : data(c.handler(data, opaque_handle_operation::Clone)) , handler(c.handler){} constexpr object_handler(object_handler&& m) noexcept : data(std::exchange(m.data, nullptr)) , handler(std::move(m.handler)){} constexpr object_handler(T* data, HandleF&& handler) noexcept(is_nothrow_ctor) : data(data) , handler(std::move(handler)){} constexpr /*virtual*/ ~object_handler() noexcept(is_nothrow) { if(data) handler(data, opaque_handle_operation::Delete); } T* data; HandleF handler; }; return make_opaque_object_handle(object_handler{data, std::forward(handler)}); } /// Create and opaque_handle from a data pointer and a constexpr handler funcion template param (e.g. a lambda) /// /// `Func` should be in the form: `auto* (T*, opaque_handle_operation) [noexcept]`. It must be a constant expression. template auto Func> constexpr inline opaque_handle make_opaque_handle(T* data) noexcept { using HandleF = decltype(Func); constexpr bool is_nothrow = std::is_nothrow_invocable_v; struct object_handler { constexpr object_handler(const object_handler& c) noexcept(is_nothrow) : data(Func(data, opaque_handle_operation::Clone)){} constexpr object_handler(object_handler&& m) noexcept : data(std::exchange(m.data, nullptr)){} constexpr object_handler(T* data) noexcept : data(data){} constexpr /*virtual*/ ~object_handler() noexcept(is_nothrow) { if(data) Func(data, opaque_handle_operation::Delete); } T* data; }; return make_opaque_object_handle(object_handler{data}); }