//! TODO: Document this header, then turn it into a single-header library. It's useful. We can make `src/main.cpp` our test program.
//! TODO: Put impl-related stuff into a private namespace maybe? or at least a [[gnu::visibility("internal"/"hidden")]] one? Or perhaps their polymorphism is such that the impl namespace shouldn't be private idk... At most, internal, I'd guess. but it'd need testing.
# pragma once
# include <utility>
# include <stdexcept>
# include <typeinfo>
# 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 " ) ;
# 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 ;
template < typename T >
constexpr inline bool opaque_is_incomplete = ! requires { { sizeof ( T ) } ; } ;
struct opaque_handle_impl {
friend class opaque_handle ;
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 >
inline constexpr const To * try_cast ( ) const noexcept { return is_type < To > ( ) ? static_cast < const To * > ( as_raw_ptr ( ) ) : nullptr ; }
template < typename To >
inline constexpr To * try_cast ( ) noexcept { return is_type < To > ( ) ? static_cast < To * > ( as_raw_ptr ( ) ) : nullptr ; }
template < typename T >
constexpr bool is_type ( ) const noexcept {
if constexpr ( requires { { sizeof ( T ) } ; } ) return is_type ( typeid ( T ) ) ; else return true ;
}
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 :
constexpr virtual void * as_raw_ptr ( ) const noexcept = 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 {
if ( auto * np = try_clone ( ) ) return * np ;
throw opaque_not_copyable { } ;
}
constexpr opaque_handle_impl ( ) noexcept { }
} ;
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 opaque_handle ( opaque_handle & & move ) noexcept
: _impl ( std : : exchange ( move . _impl , nullptr ) ) { }
inline opaque_handle ( const opaque_handle & copy )
: _impl ( copy . _impl
? copy . _impl - > try_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 < typename T , bool Reinterpret = false >
constexpr const T * unsafe_cast ( ) const noexcept { return _impl ? _impl - > unsafe_cast < T , Reinterpret > ( ) : nullptr ; }
template < typename T , bool Reinterpret = false >
constexpr T * unsafe_cast ( ) noexcept { return _impl ? _impl - > unsafe_cast < T , Reinterpret > ( ) : nullptr ; }
/// WARNING: try_cast<T>(), where T is an incomplete type, will always succeed, which may lead to undefined behaviour. Make sure to account for this
template < typename T >
constexpr const T * try_cast ( ) const noexcept { return _impl ? _impl - > try_cast < T > ( ) : nullptr ; }
template < typename T >
constexpr T * try_cast ( ) noexcept { return _impl ? _impl - > try_cast < T > ( ) : nullptr ; }
/// ptr_cast(): use try_cast() for complete types, and unsafe_cast() for incomplete types.
template < typename T >
constexpr T * ptr_cast ( ) noexcept {
if constexpr ( opaque_is_incomplete < T > ) return unsafe_cast < T > ( ) ;
else return try_cast < T > ( ) ;
}
template < typename T >
constexpr const T * ptr_cast ( ) const noexcept {
if constexpr ( opaque_is_incomplete < T > ) return unsafe_cast < T > ( ) ;
else return try_cast < T > ( ) ;
}
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 explicit operator bool ( ) const noexcept { return has_value ( ) ; }
// XXX: NOTE: operator-> and operator* do not, and SHOULD NOT, work for incomplete types. only `ptr_cast()` and `operator [const] T*()` should consider the completeness of the type.
//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 >
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 >
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 > ( ) ) ;
}
[[gnu::artificial]]
inline constexpr auto operator * ( ) const noexcept {
return _impl_deref_const { * this } ;
}
[[gnu::artificial]]
inline constexpr auto operator * ( ) noexcept {
return _impl_deref { * this } ;
}
/*
template < typename T >
inline auto operator * ( ) noexcept { return * ( try_cast < T > ( ) ? : throw opaque_bad_cast { } ) ; }
*/
template < typename T >
constexpr operator T * ( ) noexcept {
return ptr_cast < T > ( ) ;
}
template < typename T >
constexpr operator const T * ( ) const noexcept {
return ptr_cast < T > ( ) ;
}
private :
struct _impl_deref_const {
const opaque_handle & ptr ;
template < typename T >
[[gnu::artificial, gnu::always_inline]]
inline operator const T & ( ) & & {
return ptr . cast < T > ( ) ;
}
template < typename T >
[[gnu::artificial, gnu::always_inline]]
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 {
opaque_handle & ptr ;
template < typename T >
inline operator T & ( ) & & {
return ptr . cast < T > ( ) ; //*(ptr.try_cast<T>() ?: throw opaque_bad_cast{});
}
template < typename T >
inline operator const T & ( ) const & & {
return ptr . cast < T > ( ) ; //*(ptr.try_cast<T>() ?: throw opaque_bad_cast{});
}
} ;
opaque_handle_impl * _impl ;
} ;
template < typename T >
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 < T > )
: _obj ( std : : move ( value ) ) { }
inline constexpr virtual ~ opaque_handle_object ( ) { }
protected :
inline constexpr void * as_raw_ptr ( ) const noexcept override { return const_cast < void * > ( static_cast < const void * > ( & _obj ) /*?: (static_cast<void*>(&_obj) ?: &_obj)*/ ) ; }
inline constexpr opaque_handle_impl * try_clone ( ) const override {
if constexpr ( std : : is_copy_constructible_v < T > ) {
return new opaque_handle_object < T > { T { _obj } } ;
} else return nullptr ;
}
inline constexpr const std : : type_info * get_type_info ( ) const noexcept override { return & typeid ( T ) ; }
private :
T _obj ;
} ;
template < typename T >
opaque_handle_object ( T & & ) - > opaque_handle_object < T > ;
/// Make an opaque_handle from an object.
template < typename T >
constexpr inline opaque_handle make_opaque_object_handle ( T & & value ) noexcept ( std : : is_nothrow_move_constructible_v < T > )
{
return opaque_handle { static_cast < opaque_handle_impl * > ( new opaque_handle_object < T > ( std : : forward < T > ( value ) ) ) } ;
}
enum class opaque_handle_operation
{
Clone ,
Delete ,
} ;
template < typename F , typename T >
concept OpaqueHandleFunc = std : : is_invocable_v < F , T * , opaque_handle_operation >
& & std : : is_convertible_v < std : : invoke_result_t < F , T * , opaque_handle_operation > , 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 < 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
{
# define is_nothrow (std::is_nothrow_invocable_v<HandleF, T*, opaque_handle_operation>)
struct object_handler final : opaque_handle_impl
{
constexpr object_handler ( const object_handler & c ) noexcept ( is_nothrow )
: data ( c . handler ( c . 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 explicit 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 ) ;
}
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 ; }
inline constexpr const std : : type_info * get_type_info ( ) const noexcept override final {
if constexpr ( requires { { sizeof ( T ) } ; } )
return & typeid ( T ) ;
else return nullptr ;
}
T * data ;
const HandleF & 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.
///
/// `handler` should be in the form: `auto* (T*, opaque_handle_operation) [noexcept]`. Its lifetime is managed by the returned opaque_handle
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 > )
{
# define is_nothrow (std::is_nothrow_invocable_v<HandleF, T*, opaque_handle_operation>)
# define is_nothrow_ctor (std::is_nothrow_move_constructible_v<HandleF>)
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 > )
: data ( c . handler ( c . 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 explicit 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 ) ;
}
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 ;
}
inline constexpr const std : : type_info * get_type_info ( ) const noexcept override final {
if constexpr ( requires { { sizeof ( T ) } ; } )
return & typeid ( T ) ;
else return nullptr ;
}
T * data ;
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)
///
/// `Func` should be in the form: `auto* (T*, opaque_handle_operation) [noexcept]`. It must be a constant expression.
template < typename T , OpaqueHandleFunc < T > auto Func >
constexpr inline opaque_handle make_opaque_handle ( T * data ) noexcept
{
using HandleF = decltype ( Func ) ;
# define is_nothrow (std::is_nothrow_invocable_v<HandleF, T*, opaque_handle_operation>)
struct object_handler final : opaque_handle_impl
{
constexpr object_handler ( const object_handler & c ) noexcept ( is_nothrow )
: data ( Func ( c . data , opaque_handle_operation : : Clone ) ) { }
constexpr object_handler ( object_handler & & m ) noexcept
: data ( std : : exchange ( m . data , nullptr ) ) { }
constexpr explicit object_handler ( T * data ) noexcept
: data ( data ) { }
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 < T * > ( data ) ; }
constexpr opaque_handle_impl * try_clone ( ) const override final
{ return data ? new object_handler ( * this ) : nullptr ; }
inline constexpr const std : : type_info * get_type_info ( ) const noexcept override final {
if constexpr ( requires { { sizeof ( T ) } ; } )
return & typeid ( T ) ;
else return nullptr ;
}
T * data ;
} ;
return opaque_handle ( static_cast < opaque_handle_impl * > ( new object_handler ( data ) ) ) ;
# undef is_nothrow
}
# undef $_oe_msg
# undef $_oe