diff --git a/include/opaque.hh b/include/opaque.hh index da10c4f..fbee0da 100644 --- a/include/opaque.hh +++ b/include/opaque.hh @@ -20,6 +20,16 @@ struct opaque_handle_impl; template constexpr inline bool opaque_is_incomplete = !requires { { sizeof(T) }; }; +#define $impl _opaque__impl +namespace $impl { + template + concept CompleteType = !opaque_is_incomplete; +} + +/// A handler that always fails, only really useful for `make_opaque_handle`s that start with null pointers or ones that do not need to be freed and cannot be copied. +template +constexpr inline auto opaque_empty_handler = [] [[gnu::const, gnu::always_inline]] (auto*, auto) noexcept -> T* { return nullptr; }; + struct opaque_handle_impl { friend class opaque_handle; @@ -47,7 +57,7 @@ struct opaque_handle_impl { template constexpr bool is_type() const noexcept { - if constexpr(requires { { sizeof(T) }; }) return is_type(typeid(T)); else return true; + if constexpr(requires { { sizeof(T) }; }) return is_type(typeid(T)); else return false; //XXX: Should this be `true`? } constexpr virtual bool is_type(const std::type_info& a) const noexcept { if(const auto* p = get_type_info()) return *p == a; @@ -98,9 +108,21 @@ struct opaque_handle final { /// WARNING: try_cast(), where T is an incomplete type, will always succeed, which may lead to undefined behaviour. Make sure to account for this template - constexpr const T* try_cast() const noexcept { return _impl ? _impl->try_cast() : nullptr; } + constexpr const T* try_cast() const noexcept { static_assert(!opaque_is_incomplete, "try_cast always returns nullptr when T is an incomplete type, use `ptr_cast()` if T may be incomplete, or `unsafe_cast()` is it is known to be incomplete"); + return _impl ? _impl->try_cast() : nullptr; } template - constexpr T* try_cast() noexcept { return _impl ? _impl->try_cast() : nullptr; } + constexpr T* try_cast() noexcept { static_assert(!opaque_is_incomplete, "try_cast always returns nullptr when T is an incomplete type, use `ptr_cast()` if T may be incomplete, or `unsafe_cast()` if it is known to be incomplete."); + return _impl ? _impl->try_cast() : nullptr; } + + template<$impl::CompleteType T> + constexpr bool is_type() const noexcept { + return is_type(typeid(T)); + } + + constexpr bool is_type(const std::type_info& ty) const noexcept + { + return _impl ? _impl->is_type(ty) : false; + } /// ptr_cast(): use try_cast() for complete types, and unsafe_cast() for incomplete types. @@ -115,14 +137,15 @@ struct opaque_handle final { else return try_cast(); } - template + template<$impl::CompleteType T, cast_kind Kind = CAST_SAFE> inline const T& cast() const { if constexpr(Kind >= CAST_UNSAFE) return *(unsafe_cast() ?: throw opaque_bad_cast{}); - else + else { // CAST_SAFE return *(try_cast() ?: throw opaque_bad_cast{}); + } } - template + template<$impl::CompleteType T, cast_kind Kind = CAST_SAFE> inline T& cast() { if constexpr(Kind >= CAST_UNSAFE) return *(unsafe_cast() ?: throw opaque_bad_cast{}); @@ -230,28 +253,32 @@ opaque_handle_object(T&&) -> opaque_handle_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)))}; + return opaque_handle{static_cast(new opaque_handle_object(std::move(value)))}; +} + +/// Make an opaque_handle from a newly constructed object `T`. +template requires(std::is_constructible_v) +constexpr inline opaque_handle make_opaque_object_handle(Args&&... ctor) noexcept(std::is_nothrow_constructible_v) +{ + return opaque_handle{static_cast(new opaque_handle_object(std::forward(ctor)...))}; } +/// Operation for `OpaqueHandleFunc` functions. enum class opaque_handle_operation { Clone, Delete, }; - +/// A function that handles the de-allocation and cloning (if supported) of an opaque_handle created with `make_opaque_handle`. template concept OpaqueHandleFunc = std::is_invocable_v && std::is_convertible_v, T*>; -//TODO: Turn all local `constexpr is_nothrow` in the following functions into #define macros. The constexpr locals being used in a local class cause the compiler to segfault for some reason... - -//XXX: TODO: `make_opaque_handle()`'s local classes cannot be casted back to `T*`, adding operator T* won't help here because they are static/dynamic casted. We need them to inherit `opaque_handle_impl` - /// 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. +template HandleF> constexpr inline opaque_handle make_opaque_handle(T* data, const HandleF& handler) noexcept { #define is_nothrow (std::is_nothrow_invocable_v) @@ -270,7 +297,7 @@ constexpr inline opaque_handle make_opaque_handle(T* data, const HandleF& handle constexpr virtual ~object_handler() //noexcept(is_nothrow) { - if(data) handler(data, opaque_handle_operation::Delete); + if(data) (void)handler(data, opaque_handle_operation::Delete); } inline constexpr void* as_raw_ptr() const noexcept override final @@ -316,7 +343,7 @@ constexpr inline opaque_handle make_opaque_handle(T* data, HandleF&& handler) no constexpr virtual ~object_handler() //noexcept(is_nothrow) { - if(data) handler(data, opaque_handle_operation::Delete); + if(data) (void)handler(data, opaque_handle_operation::Delete); } inline constexpr void* as_raw_ptr() const noexcept override final @@ -364,7 +391,7 @@ constexpr inline opaque_handle make_opaque_handle(T* data) noexcept constexpr virtual ~object_handler() //noexcept(is_nothrow) { - if(data) Func(data, opaque_handle_operation::Delete); + if(data) (void)Func(data, opaque_handle_operation::Delete); } inline constexpr void* as_raw_ptr() const noexcept override final @@ -386,3 +413,4 @@ constexpr inline opaque_handle make_opaque_handle(T* data) noexcept #undef $_oe_msg #undef $_oe +#undef $impl diff --git a/src/main.cpp b/src/main.cpp index 0475c13..5b8d97d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -116,8 +116,7 @@ static void use_c_struct() using type = c_struct_example; auto* allocp = cs_create(nullptr); - opaque_handle impossible{make_opaque_handle(nullptr, [](auto*, auto) noexcept { return static_cast(nullptr); })}; - + opaque_handle impossible{make_opaque_handle>(nullptr)}; black_box(impossible); const opaque_handle v{make_opaque_handle(allocp, [](auto* ptr, auto op) noexcept { @@ -144,8 +143,8 @@ static void use_c_struct() int main() { const - opaque_handle v{make_opaque_object_handle(std::string{"Hello world"})}; - + opaque_handle v{make_opaque_object_handle("Hello world")}; + puts("Auto-downcasting to `const std::string&`:"); print(*v); #if 1