Added rudimentary type-checking to make `try_cast` work. However, this breaks use with incomplete types, which is not acceptable...

Fortune for opaque_handle's current commit: Half blessing − 半吉
master
Avril 3 years ago
parent 437037e8c6
commit 4f1481b589
Signed by: flanchan
GPG Key ID: 284488987C31F630

@ -2,6 +2,7 @@
#include <utility> #include <utility>
#include <stdexcept> #include <stdexcept>
#include <typeinfo>
#define $_oe_msg(msg) { inline const char* what() const noexcept override { return (msg); } } #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"); struct opaque_exception : std::exception $_oe_msg("unspecified opaque_handle related error");
@ -19,15 +20,34 @@ struct opaque_handle_impl {
virtual constexpr ~opaque_handle_impl() {} virtual constexpr ~opaque_handle_impl() {}
template<typename To, bool Reinterpret=false>
inline constexpr const To* unsafe_cast() const noexcept {
if constexpr(Reinterpret)
return reinterpret_cast<const To*>(as_raw_ptr());
else return static_cast<const To*>(as_raw_ptr());
}
template<typename To, bool Reinterpret=false>
inline constexpr To* unsafe_cast() noexcept {
if constexpr(Reinterpret)
return reinterpret_cast<To*>(as_raw_ptr());
else return static_cast<To*>(as_raw_ptr());
}
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 is_type(typeid(To)) ? static_cast<const To*>(as_raw_ptr()) : nullptr; }
template<typename To> template<typename To>
inline constexpr To* try_cast() noexcept { return static_cast<To*>(as_raw_ptr()); } inline constexpr To* try_cast() noexcept { return is_type(typeid(To)) ? static_cast<To*>(as_raw_ptr()) : nullptr; }
constexpr virtual bool is_type(const std::type_info& a) const noexcept {
if(const auto* p = get_type_info()) return *p == a;
return false; // Types are uncomparable.
}
protected: protected:
constexpr virtual void* as_raw_ptr() const noexcept = 0; constexpr virtual void* as_raw_ptr() const noexcept = 0;
constexpr virtual opaque_handle_impl* try_clone() const = 0; constexpr virtual opaque_handle_impl* try_clone() const = 0;
constexpr virtual const std::type_info* get_type_info() const noexcept =0;
inline virtual opaque_handle_impl& clone() const { inline virtual opaque_handle_impl& clone() const {
if(auto* np = try_clone()) return *np; if(auto* np = try_clone()) return *np;
@ -38,6 +58,12 @@ protected:
}; };
struct opaque_handle final { struct opaque_handle final {
enum cast_kind {
CAST_SAFE,
CAST_UNSAFE,
CAST_UNSAFE_REINTERPRET,
};
constexpr explicit opaque_handle(opaque_handle_impl* ptr) noexcept : _impl(ptr){} constexpr explicit opaque_handle(opaque_handle_impl* ptr) noexcept : _impl(ptr){}
constexpr opaque_handle(opaque_handle&& move) noexcept constexpr opaque_handle(opaque_handle&& move) noexcept
@ -55,32 +81,56 @@ 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; }
template<typename T, bool Reinterpret=false>
constexpr const T* unsafe_cast() const noexcept { return _impl ? _impl->unsafe_cast<T, Reinterpret>() : 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, bool Reinterpret=false>
template<typename T> constexpr T* unsafe_cast() noexcept { return _impl ? _impl->unsafe_cast<T, Reinterpret>() : nullptr; }
/// WARNING: Unsafe, see above
constexpr const T* try_cast() const noexcept { return static_cast<const T*>(as_ptr()); }
template<typename T>
/// WARNING: Unsafe, see above
constexpr T* try_cast() noexcept { return static_cast<T*>(as_ptr()); }
template<typename T> template<typename T>
inline const T& cast() const { return *(try_cast<T>() ?: throw opaque_bad_cast{}); } constexpr const T* try_cast() const noexcept { return _impl ? _impl->try_cast<T>() : nullptr; }
template<typename T> template<typename T>
inline T& cast() { return *(try_cast<T>() ?: throw opaque_bad_cast{}); } constexpr T* try_cast() noexcept { return _impl ? _impl->try_cast<T>() : nullptr; }
template<typename T, cast_kind Kind = CAST_SAFE>
inline const T& cast() const {
if constexpr(Kind >= CAST_UNSAFE)
return *(unsafe_cast<T, (Kind == CAST_UNSAFE_REINTERPRET)>() ?: throw opaque_bad_cast{});
else
return *(try_cast<T>() ?: throw opaque_bad_cast{});
}
template<typename T, cast_kind Kind = CAST_SAFE>
inline T& cast() {
if constexpr(Kind >= CAST_UNSAFE)
return *(unsafe_cast<T, (Kind == CAST_UNSAFE_REINTERPRET)>() ?: throw opaque_bad_cast{});
else
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(); }
//TODO: Test these, we may need to do the same thing we did for `operator*`, but with a shim that has `operator T*` and `operator->` instead of `operator T&`.
template<typename T> template<typename T>
constexpr const T* operator->() const noexcept { return try_cast<T>(); } constexpr const T* operator->() const noexcept(std::is_void_v<T>) {
if constexpr(std::is_void_v<T>)
return as_ptr();
else return std::addressof(cast<T>());
}
template<typename T> template<typename T>
constexpr T* operator->() noexcept { return try_cast<T>(); } //XXX: TODO: throw if try_cast<T>() is null and T is not `void`. constexpr T* operator->() noexcept(std::is_void_v<T>)
{
if constexpr(std::is_void_v<T>)
return as_ptr();
else return std::addressof(cast<T>());
}
inline auto operator*() const noexcept { [[gnu::artificial]]
inline constexpr auto operator*() const noexcept {
return _impl_deref_const{*this}; return _impl_deref_const{*this};
} }
inline auto operator*() noexcept { [[gnu::artificial]]
inline constexpr auto operator*() noexcept {
return _impl_deref{*this}; return _impl_deref{*this};
} }
/* /*
@ -97,12 +147,14 @@ private:
const opaque_handle& ptr; const opaque_handle& ptr;
template<typename T> template<typename T>
[[gnu::artificial, gnu::always_inline]]
inline operator const T&() && { inline operator const T&() && {
return *(ptr.try_cast<T>() ?: throw opaque_bad_cast{}); return ptr.cast<T>();
} }
template<typename T> template<typename T>
inline operator const T&() const&& { [[gnu::artificial, gnu::always_inline]]
return *(ptr.try_cast<T>() ?: throw opaque_bad_cast{}); inline operator T() const&& { // For this to work, this method must return `T`, i'm not sure *why*, but oh well. It doesn't seem to incur any extra overhead, so... okay.
return ptr.cast<T>();
} }
}; };
struct _impl_deref { struct _impl_deref {
@ -110,11 +162,11 @@ private:
template<typename T> template<typename T>
inline operator T&() && { inline operator T&() && {
return *(ptr.try_cast<T>() ?: throw opaque_bad_cast{}); return ptr.cast<T>(); //*(ptr.try_cast<T>() ?: throw opaque_bad_cast{});
} }
template<typename T> template<typename T>
inline operator const T&() const&& { inline operator const T&() const&& {
return *(ptr.try_cast<T>() ?: throw opaque_bad_cast{}); return ptr.cast<T>(); //*(ptr.try_cast<T>() ?: throw opaque_bad_cast{});
} }
}; };
opaque_handle_impl* _impl; opaque_handle_impl* _impl;
@ -138,6 +190,7 @@ protected:
return new opaque_handle_object<T>{T{_obj}}; return new opaque_handle_object<T>{T{_obj}};
} else return nullptr; } else return nullptr;
} }
inline constexpr const std::type_info* get_type_info() const noexcept override { return &typeid(T); }
private: private:
T _obj; T _obj;
}; };
@ -195,6 +248,7 @@ constexpr inline opaque_handle make_opaque_handle(T* data, const HandleF& handle
{ return const_cast<T*>(data); } { return const_cast<T*>(data); }
constexpr opaque_handle_impl* try_clone() const override final constexpr opaque_handle_impl* try_clone() const override final
{ return data ? new object_handler(*this) : nullptr; } { return data ? new object_handler(*this) : nullptr; }
inline constexpr const std::type_info* get_type_info() const noexcept override final { return &typeid(T); }
T* data; T* data;
const HandleF& handler; const HandleF& handler;
@ -240,6 +294,7 @@ constexpr inline opaque_handle make_opaque_handle(T* data, HandleF&& handler) no
return data ? new object_handler(*this) : nullptr; return data ? new object_handler(*this) : nullptr;
} else return nullptr; } else return nullptr;
} }
inline constexpr const std::type_info* get_type_info() const noexcept override final { return &typeid(T); }
T* data; T* data;
HandleF handler; HandleF handler;
@ -279,6 +334,7 @@ constexpr inline opaque_handle make_opaque_handle(T* data) noexcept
{ return const_cast<T*>(data); } { return const_cast<T*>(data); }
constexpr opaque_handle_impl* try_clone() const override final constexpr opaque_handle_impl* try_clone() const override final
{ return data ? new object_handler(*this) : nullptr; } { return data ? new object_handler(*this) : nullptr; }
inline constexpr const std::type_info* get_type_info() const noexcept override final { return &typeid(T); }
T* data; T* data;
}; };

@ -136,11 +136,29 @@ static void use_c_struct()
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"})};
puts("Auto-downcasting to `const std::string&`:"); puts("Auto-downcasting to `const std::string&`:");
print(*v); print(*v);
#if 1
s_puts("\nAttempting invalid safe cast... ");
try {
print_view(*v); // Try to cast to std::string_view, not the correct type...
s_puts(" FAILED\n");
return 1;
} catch(opaque_bad_cast&) { // Expected this exception to be thrown
s_puts(" OK\n");
} catch(opaque_exception& other) {
s_puts(" UNEXPECTED (");
s_puts(other.what());
s_puts(")\n");
return 2;
}
#endif
puts("\nConverting view directly:"); puts("\nConverting view directly:");
print_view(std::string_view{v.cast<std::string>()}); // Explicit cast needed, *v will choose the wrong type if not. print_view(std::string_view{v.cast<std::string, opaque_handle::CAST_UNSAFE /* SAFETY: We know this is the type of `v`'s pointee, so we don't need the type check. */>()}); // Explicit cast needed, *v will choose the wrong type if not.
puts("\nUsing opaque_make_handle():"); puts("\nUsing opaque_make_handle():");
use_moh<moh_hck::Template>(); use_moh<moh_hck::Template>();

Loading…
Cancel
Save