Tested all variants of `make_opaque_handle()`, they all work. `opaque_handle` workable in a project; however. `try_cast()` does not work correctly and will not return `nullptr` if the types are wrong. TODO: Fixing this will require a refactor to use another inheritance graph of objects instead of `void*`, however, this is intended for work using C interfaces, so it"s not a high priority for it to work on polymorphic types.

Fortune for opaque_handle's current commit: Small blessing − 小吉
master
Avril 3 years ago
parent e8813d52eb
commit d0558318b0
Signed by: flanchan
GPG Key ID: 284488987C31F630

8
.gitignore vendored

@ -1,3 +1,11 @@
# Source
*~ *~
# Build
*.o *.o
a.out a.out
# Debugging
vgcore.*
massif.out.*
memusage.*

@ -3,63 +3,13 @@
#include <utility> #include <utility>
#include <stdexcept> #include <stdexcept>
#if 0 //XXX: This doesn't work, we need `opaque_handle` to not be a template... #define $_oe_msg(msg) { inline const char* what() const noexcept override { return (msg); } }
namespace _opaque_impl { struct opaque_exception : std::exception $_oe_msg("unspecified opaque_handle related error");
struct opaque_handle {
virtual constexpr ~opaque_handle(); #define $_oe(msg) public opaque_exception $_oe_msg((msg))
protected: struct opaque_not_copyable : $_oe("opaque_handle referred to a type that was not copyable");
inline constexpr explicit opaque_handle(void* data) noexcept : _ptr(data){} struct opaque_handle_moved : $_oe("this opaque_handle was moved");
template<typename From> struct opaque_bad_cast : $_oe("bad opaque_handle pointer cast");
inline constexpr static opaque_handle from_object(T& ptr) noexcept
{
return opaque_handle{dynamic_cast<void*>(&ptr) ?: static_cast<void*>(&ptr)};
}
inline constexpr void* ptr() const noexcept {
return _ptr;
}
template<typename To>
inline constexpr T* downcast() const noexcept {
return dynamic_cast<To*>(_ptr);
}
template<typename To>
inline constexpr downcast_unsafe() const noexcept {
return static_cast<To*>(_ptr);
}
template<typename To>
inline T* ptr_as() { return std::bit_cast<To>(_ptr); }
private:
void* _ptr;
};
template<typename T>
struct opaque_handle_for : opaque_handle
{
constexpr opaque_handle_for(T&& value)
: opaque_handle(opaque_handle::template from_object<T>(*new T(std::move(value)))){}
constexpr virtual ~opaque_handle_for()
{
if(T* ptr = downcast_unsafe<T>()) {
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;
struct opaque_handle_impl; struct opaque_handle_impl;
@ -67,7 +17,7 @@ struct opaque_handle_impl;
struct opaque_handle_impl { struct opaque_handle_impl {
friend class opaque_handle; friend class opaque_handle;
virtual constexpr ~opaque_handle_impl() noexcept {} virtual constexpr ~opaque_handle_impl() {}
template<typename To> template<typename To>
inline constexpr const To* try_cast() const noexcept { return static_cast<const To*>(as_raw_ptr()); } inline constexpr const To* try_cast() const noexcept { return static_cast<const To*>(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 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; } 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<typename T> template<typename T>
/// WARNING: Unsafe, see above
constexpr const T* try_cast() const noexcept { return static_cast<const T*>(as_ptr()); } constexpr const T* try_cast() const noexcept { return static_cast<const T*>(as_ptr()); }
template<typename T> template<typename T>
/// WARNING: Unsafe, see above
constexpr T* try_cast() noexcept { return static_cast<T*>(as_ptr()); } constexpr T* try_cast() noexcept { return static_cast<T*>(as_ptr()); }
template<typename T>
inline const T& cast() const { return *(try_cast<T>() ?: throw opaque_bad_cast{}); }
template<typename T>
inline T& cast() { return *(try_cast<T>() ?: throw opaque_bad_cast{}); }
constexpr bool has_value() const noexcept { return bool(_impl); } constexpr bool has_value() const noexcept { return bool(_impl); }
constexpr explicit operator bool() const noexcept { return has_value(); } constexpr explicit operator bool() const noexcept { return has_value(); }
@ -205,15 +164,17 @@ concept OpaqueHandleFunc = std::is_invocable_v<F, T*, opaque_handle_operation>
//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... //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 /// 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`. /// `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<typename T, OpaqueHandleFunc<T> 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<typename T, OpaqueHandleFunc<T> 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 inline opaque_handle make_opaque_handle(T* data, const HandleF& handler) noexcept
{ {
constexpr const bool is_nothrow = std::is_nothrow_invocable_v<HandleF, T*, opaque_handle_operation>; #define is_nothrow (std::is_nothrow_invocable_v<HandleF, T*, opaque_handle_operation>)
struct object_handler struct object_handler final : opaque_handle_impl
{ {
constexpr object_handler(const object_handler& c) noexcept(is_nothrow) constexpr object_handler(const object_handler& c) noexcept(is_nothrow)
: data(c.handler(data, opaque_handle_operation::Clone)) : 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) : data(data)
, handler(handler){} , handler(handler){}
constexpr /*virtual*/ ~object_handler() noexcept(is_nothrow) constexpr virtual ~object_handler() //noexcept(is_nothrow)
{ {
if(data) handler(data, opaque_handle_operation::Delete); if(data) handler(data, opaque_handle_operation::Delete);
} }
inline constexpr void* as_raw_ptr() const noexcept override final
{ return const_cast<T*>(data); }
constexpr opaque_handle_impl* try_clone() const override final
{ return data ? new object_handler(*this) : nullptr; }
T* data; T* data;
const HandleF& handler; const HandleF& handler;
}; };
return make_opaque_object_handle(object_handler{data, handler}); return opaque_handle(static_cast<opaque_handle_impl*>(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. /// 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<typename T, OpaqueHandleFunc<T> HandleF> template<typename T, OpaqueHandleFunc<T> HandleF>
constexpr inline opaque_handle make_opaque_handle(T* data, HandleF&& handler) noexcept(std::is_nothrow_move_constructible_v<HandleF>) constexpr inline opaque_handle make_opaque_handle(T* data, HandleF&& handler) noexcept(std::is_nothrow_move_constructible_v<HandleF>)
{ {
constexpr bool is_nothrow = std::is_nothrow_invocable_v<HandleF, T*, opaque_handle_operation>; //XXX: Causes segfault... TODO: turn these into a #define macro #define is_nothrow (std::is_nothrow_invocable_v<HandleF, T*, opaque_handle_operation>)
constexpr bool is_nothrow_ctor = std::is_nothrow_move_constructible_v<HandleF>; #define is_nothrow_ctor (std::is_nothrow_move_constructible_v<HandleF>)
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<HandleF>) requires(std::is_copy_constructible_v<HandleF>) constexpr object_handler(const object_handler& c) noexcept(is_nothrow && std::is_nothrow_copy_constructible_v<HandleF>) requires(std::is_copy_constructible_v<HandleF>)
: data(c.handler(data, opaque_handle_operation::Clone)) : 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) : data(data)
, handler(std::move(handler)){} , 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); if(data) handler(data, opaque_handle_operation::Delete);
} }
inline constexpr void* as_raw_ptr() const noexcept override final
{ return const_cast<T*>(data); }
constexpr opaque_handle_impl* try_clone() const override final
{
if constexpr(std::is_copy_constructible_v<HandleF>) {
return data ? new object_handler(*this) : nullptr;
} else return nullptr;
}
T* data; T* data;
HandleF handler; HandleF handler;
}; };
return make_opaque_object_handle(object_handler{data, std::forward<HandleF>(handler)}); return opaque_handle(static_cast<opaque_handle_impl*>(new object_handler(data, std::forward<HandleF>(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) /// Create and opaque_handle from a data pointer and a constexpr handler funcion template param (e.g. a lambda)
@ -278,12 +256,12 @@ template<typename T, OpaqueHandleFunc<T> auto Func>
constexpr inline opaque_handle make_opaque_handle(T* data) noexcept constexpr inline opaque_handle make_opaque_handle(T* data) noexcept
{ {
using HandleF = decltype(Func); using HandleF = decltype(Func);
constexpr bool is_nothrow = std::is_nothrow_invocable_v<HandleF, T*, opaque_handle_operation>; #define is_nothrow (std::is_nothrow_invocable_v<HandleF, T*, opaque_handle_operation>)
struct object_handler struct object_handler final : opaque_handle_impl
{ {
constexpr object_handler(const object_handler& c) noexcept(is_nothrow) 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 constexpr object_handler(object_handler&& m) noexcept
: data(std::exchange(m.data, nullptr)){} : 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 constexpr object_handler(T* data) noexcept
: data(data){} : data(data){}
constexpr /*virtual*/ ~object_handler() noexcept(is_nothrow) constexpr virtual ~object_handler() //noexcept(is_nothrow)
{ {
if(data) Func(data, opaque_handle_operation::Delete); if(data) Func(data, opaque_handle_operation::Delete);
} }
inline constexpr void* as_raw_ptr() const noexcept override final
{ return const_cast<T*>(data); }
constexpr opaque_handle_impl* try_clone() const override final
{ return data ? new object_handler(*this) : nullptr; }
T* data; T* data;
}; };
return make_opaque_object_handle(object_handler{data}); return opaque_handle(static_cast<opaque_handle_impl*>(new object_handler(data)));
#undef is_nothrow
} }
#undef $_oe_msg
#undef $_oe

@ -9,13 +9,15 @@ static void print_view(std::string_view ptr)
//std::string_view ptr{string}; //std::string_view ptr{string};
size_t sz; 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)) { if(__builtin_expect((sz = ptr.size()) > 0, true)) {
write(1, "> ", 2); write(1, "> ", 2);
write(1, ptr.data(), sz); write(1, ptr.data(), sz);
} else write(1, "! <null>\n", 8); } else write(1, "! <null>\n", 8);
write(1, "\n", 1); write(1, "\n", 1);
#else #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)) { if(__builtin_expect((sz = ptr.size()) > 0, true)) {
printf("> %.*s\n", (int)sz, ptr.data()); printf("> %.*s\n", (int)sz, ptr.data());
} else puts("! <null>\n"); } else puts("! <null>\n");
@ -27,9 +29,19 @@ static inline void print(const std::string& string)
return print_view(sv); 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<moh_hck Create = moh_hck::Owned>
void use_moh() void use_moh()
{ {
std::string str{"Hello from 2!"}; std::string* str = new std::string(__PRETTY_FUNCTION__);
if constexpr(Create != moh_hck::Owned) {
constexpr auto _h = [](std::string* ptr, auto op) noexcept { constexpr auto _h = [](std::string* ptr, auto op) noexcept {
if(ptr) { if(ptr) {
switch(op) { switch(op) {
@ -39,14 +51,39 @@ void use_moh()
} }
return static_cast<std::string*>(nullptr); return static_cast<std::string*>(nullptr);
}; };
opaque_handle v{make_opaque_handle<std::string, _h>(&str)};
if constexpr(Create == moh_hck::Template) {
const opaque_handle v{make_opaque_handle<std::string, _h>(str)};
print(*v);
} else /*if constexpr(Create == moh_hck::Ref)*/ {
const opaque_handle v{make_opaque_handle<std::string>(str, _h)};
print(*v);
}
} else {
const opaque_handle v{make_opaque_handle<std::string>(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<std::string*>(nullptr);
})};
print(*v); print(*v);
}
} }
int main() int main()
{ {
const opaque_handle v{make_opaque_object_handle(std::string{"Hello world"})}; 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? puts("Auto-downcasting to `const std::string&`:");
use_moh(); print(*v);
puts("\nConverting view directly:");
print_view(std::string_view{v.cast<std::string>()}); // Explicit cast needed, *v will choose the wrong type if not.
puts("\nUsing opaque_make_handle():");
use_moh<moh_hck::Template>();
use_moh<moh_hck::Ref>();
use_moh<moh_hck::Owned>();
return 0; return 0;
} }

Loading…
Cancel
Save