diff --git a/.gitignore b/.gitignore index 8919ad2..3201bd3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,11 @@ +# Source *~ + +# Build *.o a.out + +# Debugging +vgcore.* +massif.out.* +memusage.* diff --git a/include/opaque.hh b/include/opaque.hh index fd5460e..93ce8ab 100644 --- a/include/opaque.hh +++ b/include/opaque.hh @@ -3,63 +3,13 @@ #include #include -#if 0 //XXX: This doesn't work, we need `opaque_handle` to not be a template... -namespace _opaque_impl { -struct opaque_handle { +#define $_oe_msg(msg) { inline const char* what() const noexcept override { return (msg); } } +struct opaque_exception : std::exception $_oe_msg("unspecified opaque_handle related error"); - 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{}; +#define $_oe(msg) public opaque_exception $_oe_msg((msg)) +struct opaque_not_copyable : $_oe("opaque_handle referred to a type that was not copyable"); +struct opaque_handle_moved : $_oe("this opaque_handle was moved"); +struct opaque_bad_cast : $_oe("bad opaque_handle pointer cast"); struct opaque_handle; struct opaque_handle_impl; @@ -67,7 +17,7 @@ struct opaque_handle_impl; struct opaque_handle_impl { friend class opaque_handle; - virtual constexpr ~opaque_handle_impl() noexcept {} + virtual constexpr ~opaque_handle_impl() {} template inline constexpr const To* try_cast() const noexcept { return static_cast(as_raw_ptr()); } @@ -105,11 +55,20 @@ struct opaque_handle final { 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; } + + ///XXX: These don't work as expected. And we can't dynamic_cast<>(void*), so... What do we do to check the type of `as_ptr()` is `T` without embedding all this extra type info? Another layer of inheritence, and have `as_raw_ptr()` return a pointer to that base instead of `void*`? idk... template + /// WARNING: Unsafe, see above constexpr const T* try_cast() const noexcept { return static_cast(as_ptr()); } template + /// WARNING: Unsafe, see above constexpr T* try_cast() noexcept { return static_cast(as_ptr()); } + template + inline const T& cast() const { return *(try_cast() ?: throw opaque_bad_cast{}); } + template + inline T& cast() { return *(try_cast() ?: throw opaque_bad_cast{}); } + constexpr bool has_value() const noexcept { return bool(_impl); } constexpr explicit operator bool() const noexcept { return has_value(); } @@ -205,15 +164,17 @@ concept OpaqueHandleFunc = std::is_invocable_v //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. constexpr inline opaque_handle make_opaque_handle(T* data, const HandleF& handler) noexcept { - constexpr const bool is_nothrow = std::is_nothrow_invocable_v; +#define is_nothrow (std::is_nothrow_invocable_v) - struct object_handler + struct object_handler final : opaque_handle_impl { constexpr object_handler(const object_handler& c) noexcept(is_nothrow) : data(c.handler(data, opaque_handle_operation::Clone)) @@ -225,16 +186,22 @@ constexpr inline opaque_handle make_opaque_handle(T* data, const HandleF& handle : data(data) , handler(handler){} - constexpr /*virtual*/ ~object_handler() noexcept(is_nothrow) + constexpr virtual ~object_handler() //noexcept(is_nothrow) { if(data) handler(data, opaque_handle_operation::Delete); } + inline constexpr void* as_raw_ptr() const noexcept override final + { return const_cast(data); } + constexpr opaque_handle_impl* try_clone() const override final + { return data ? new object_handler(*this) : nullptr; } + T* data; const HandleF& handler; }; - return make_opaque_object_handle(object_handler{data, handler}); + return opaque_handle(static_cast(new object_handler(data, handler))); +#undef is_nothrow } /// Create and opaque_handle from a data pointer and a move-constructible functor which handles the copying (if possible) and deleting of the object. @@ -243,10 +210,10 @@ constexpr inline opaque_handle make_opaque_handle(T* data, const HandleF& handle template HandleF> constexpr inline opaque_handle make_opaque_handle(T* data, HandleF&& handler) noexcept(std::is_nothrow_move_constructible_v) { - constexpr bool is_nothrow = std::is_nothrow_invocable_v; //XXX: Causes segfault... TODO: turn these into a #define macro - constexpr bool is_nothrow_ctor = std::is_nothrow_move_constructible_v; +#define is_nothrow (std::is_nothrow_invocable_v) +#define is_nothrow_ctor (std::is_nothrow_move_constructible_v) - struct object_handler + struct object_handler final : opaque_handle_impl { 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)) @@ -259,16 +226,27 @@ constexpr inline opaque_handle make_opaque_handle(T* data, HandleF&& handler) no : data(data) , handler(std::move(handler)){} - constexpr /*virtual*/ ~object_handler() noexcept(is_nothrow) + constexpr virtual ~object_handler() //noexcept(is_nothrow) { if(data) handler(data, opaque_handle_operation::Delete); } + inline constexpr void* as_raw_ptr() const noexcept override final + { return const_cast(data); } + constexpr opaque_handle_impl* try_clone() const override final + { + if constexpr(std::is_copy_constructible_v) { + return data ? new object_handler(*this) : nullptr; + } else return nullptr; + } + T* data; HandleF handler; }; - return make_opaque_object_handle(object_handler{data, std::forward(handler)}); + return opaque_handle(static_cast(new object_handler(data, std::forward(handler)))); +#undef is_nothrow +#undef is_nothrow_ctor } /// Create and opaque_handle from a data pointer and a constexpr handler funcion template param (e.g. a lambda) @@ -278,12 +256,12 @@ 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; +#define is_nothrow (std::is_nothrow_invocable_v) - struct object_handler + struct object_handler final : opaque_handle_impl { constexpr object_handler(const object_handler& c) noexcept(is_nothrow) - : data(Func(data, opaque_handle_operation::Clone)){} + : data(Func(c.data, opaque_handle_operation::Clone)){} constexpr object_handler(object_handler&& m) noexcept : data(std::exchange(m.data, nullptr)){} @@ -291,13 +269,22 @@ constexpr inline opaque_handle make_opaque_handle(T* data) noexcept constexpr object_handler(T* data) noexcept : data(data){} - constexpr /*virtual*/ ~object_handler() noexcept(is_nothrow) + constexpr virtual ~object_handler() //noexcept(is_nothrow) { if(data) Func(data, opaque_handle_operation::Delete); } + inline constexpr void* as_raw_ptr() const noexcept override final + { return const_cast(data); } + constexpr opaque_handle_impl* try_clone() const override final + { return data ? new object_handler(*this) : nullptr; } + T* data; }; - return make_opaque_object_handle(object_handler{data}); + return opaque_handle(static_cast(new object_handler(data))); +#undef is_nothrow } + +#undef $_oe_msg +#undef $_oe diff --git a/src/main.cpp b/src/main.cpp index d514d9e..a0ee203 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,13 +9,15 @@ static void print_view(std::string_view ptr) //std::string_view ptr{string}; size_t sz; -#if DIRECT_WRITE +#ifdef DIRECT_WRITE + // Bypass all caching of the output and immediately invoke `write()` for everything if(__builtin_expect((sz = ptr.size()) > 0, true)) { write(1, "> ", 2); write(1, ptr.data(), sz); } else write(1, "! \n", 8); write(1, "\n", 1); #else + // All the output from the program is cached and `write()` is only called once at the end. if(__builtin_expect((sz = ptr.size()) > 0, true)) { printf("> %.*s\n", (int)sz, ptr.data()); } else puts("! \n"); @@ -27,26 +29,61 @@ static inline void print(const std::string& string) return print_view(sv); } +enum class moh_hck { + Template, + Ref, + Owned, +}; + +/// Example using `make_opaque_handle`, like would be done for a C interface +template void use_moh() { - std::string str{"Hello from 2!"}; - constexpr auto _h = [](std::string* ptr, auto op) noexcept { - if(ptr) { - switch(op) { - case opaque_handle_operation::Clone: return new std::string(*ptr); - case opaque_handle_operation::Delete: delete ptr; break; + std::string* str = new std::string(__PRETTY_FUNCTION__); + + if constexpr(Create != moh_hck::Owned) { + constexpr auto _h = [](std::string* ptr, auto op) noexcept { + if(ptr) { + switch(op) { + case opaque_handle_operation::Clone: return new std::string(*ptr); + case opaque_handle_operation::Delete: delete ptr; break; + } } + return static_cast(nullptr); + }; + + if constexpr(Create == moh_hck::Template) { + const opaque_handle v{make_opaque_handle(str)}; + print(*v); + } else /*if constexpr(Create == moh_hck::Ref)*/ { + const opaque_handle v{make_opaque_handle(str, _h)}; + print(*v); } - return static_cast(nullptr); - }; - opaque_handle v{make_opaque_handle(&str)}; - print(*v); + } else { + const opaque_handle v{make_opaque_handle(str, [](std::string* ptr, auto op) noexcept { + if(ptr) { + switch(op) { + case opaque_handle_operation::Clone: return new std::string(*ptr); + case opaque_handle_operation::Delete: delete ptr; break; + } + } + return static_cast(nullptr); + })}; + print(*v); + } } int main() { const opaque_handle v{make_opaque_object_handle(std::string{"Hello world"})}; - print(*v); //XXX: Why does converting it to string_view here break it? - use_moh(); + puts("Auto-downcasting to `const std::string&`:"); + print(*v); + puts("\nConverting view directly:"); + print_view(std::string_view{v.cast()}); // Explicit cast needed, *v will choose the wrong type if not. + + puts("\nUsing opaque_make_handle():"); + use_moh(); + use_moh(); + use_moh(); return 0; }