util/strformat.h, util/delegate.h: More cleanup and future-proofing.

* util/strformat.h: Found a SFINAE trick to detect absence of stream-out
  operators. Fixes building with C++20 standard library (#6275).
* util/delegate.h: Fixed a commend and removed an unused declaration
  from MSVC member function pointer wrapper.
* util/delegate.h: Added support for discarding functoid return values
  for delegates returning void.
* util/delegate.h: Added support for using std::ref to bind non-copyable
  functoids.
This commit is contained in:
Vas Crabb 2021-09-18 01:09:23 +10:00
parent 6e3adb7199
commit d1e68f25dd
3 changed files with 136 additions and 150 deletions

View File

@ -24,6 +24,7 @@
#endif
namespace util::detail {
//**************************************************************************
// GLOBAL VARIABLES
@ -31,7 +32,7 @@
#if (USE_DELEGATE_TYPE == DELEGATE_TYPE_COMPATIBLE)
delegate_mfp::raw_mfp_data delegate_mfp::s_null_mfp = { { 0 } };
const delegate_mfp::raw_mfp_data delegate_mfp::s_null_mfp = { { 0 } };
#endif
@ -82,3 +83,5 @@ delegate_generic_function delegate_mfp::convert_to_generic(delegate_generic_clas
}
#endif
} // namespace util::detail

View File

@ -82,6 +82,7 @@
#include <cstdint>
#include <cstring>
#include <exception>
#include <functional>
#include <type_traits>
#include <typeinfo>
#include <utility>
@ -97,7 +98,9 @@
#define DELEGATE_TYPE_MSVC 2
// select which one we will be using
#if defined(__GNUC__)
#if defined(FORCE_COMPATIBLE)
#define USE_DELEGATE_TYPE DELEGATE_TYPE_COMPATIBLE
#elif defined(__GNUC__)
/* 32bit MINGW asks for different convention */
#if defined(__MINGW32__) && !defined(__x86_64) && defined(__i386__)
#define USE_DELEGATE_TYPE DELEGATE_TYPE_INTERNAL
@ -110,18 +113,11 @@
#define MEMBER_ABI
#define HAS_DIFFERENT_ABI 0
#endif
#elif defined(_MSC_VER) && defined (_M_X64)
#define MEMBER_ABI
#define HAS_DIFFERENT_ABI 0
#define USE_DELEGATE_TYPE DELEGATE_TYPE_MSVC
#elif defined(_MSC_VER) && defined(_M_X64)
#define MEMBER_ABI
#define HAS_DIFFERENT_ABI 0
#define USE_DELEGATE_TYPE DELEGATE_TYPE_MSVC
#else
#define USE_DELEGATE_TYPE DELEGATE_TYPE_COMPATIBLE
#endif
#if defined(FORCE_COMPATIBLE)
#undef USE_DELEGATE_TYPE
#undef MEMBER_ABI
#undef HAS_DIFFERENT_ABI
#define USE_DELEGATE_TYPE DELEGATE_TYPE_COMPATIBLE
#endif
@ -129,6 +125,10 @@
#define MEMBER_ABI
#define HAS_DIFFERENT_ABI 0
#endif
namespace util::detail {
//**************************************************************************
// HELPER CLASSES
//**************************************************************************
@ -148,31 +148,6 @@ class delegate_generic_class;
#endif
// ======================> delegate_late_bind
// simple polymorphic class that must be mixed into any object that is late-bound
class delegate_late_bind
{
public:
delegate_late_bind() { }
virtual ~delegate_late_bind() { }
};
// ======================> binding_type_exception
// exception that is thrown when a bind fails the dynamic_cast
class binding_type_exception : public std::exception
{
public:
binding_type_exception(const std::type_info &target_type, const std::type_info &actual_type)
: m_target_type(target_type), m_actual_type(actual_type)
{ }
const std::type_info &m_target_type;
const std::type_info &m_actual_type;
};
// ======================> delegate_traits
// delegate_traits is a meta-template that is used to provide a static function pointer
@ -212,11 +187,7 @@ public:
{ }
// copy constructor
delegate_mfp(const delegate_mfp &src)
: m_rawdata(src.m_rawdata)
, m_realobject(src.m_realobject)
, m_stubfunction(src.m_stubfunction)
{ }
delegate_mfp(const delegate_mfp &src) = default;
// construct from any member function pointer
template <typename MemberFunctionType, class MemberFunctionClass, typename ReturnType, typename StaticFunctionType>
@ -225,7 +196,7 @@ public:
, m_realobject(nullptr)
, m_stubfunction(make_generic<StaticFunctionType>(&delegate_mfp::method_stub<MemberFunctionClass, ReturnType>))
{
assert(sizeof(mfp) <= sizeof(m_rawdata));
static_assert(sizeof(mfp) <= sizeof(m_rawdata), "Unsupported member function pointer size");
*reinterpret_cast<MemberFunctionType *>(&m_rawdata) = mfp;
}
@ -280,7 +251,8 @@ private:
raw_mfp_data m_rawdata; // raw buffer to hold the copy of the function pointer
delegate_generic_class * m_realobject; // pointer to the object used for calling
delegate_generic_function m_stubfunction; // pointer to our matching stub function
static raw_mfp_data s_null_mfp; // nullptr mfp
static const raw_mfp_data s_null_mfp; // nullptr mfp
};
#elif (USE_DELEGATE_TYPE == DELEGATE_TYPE_INTERNAL)
@ -292,28 +264,22 @@ class delegate_mfp
{
public:
// default constructor
delegate_mfp()
: m_function(0)
, m_this_delta(0)
{ }
delegate_mfp() = default;
// copy constructor
delegate_mfp(const delegate_mfp &src)
: m_function(src.m_function)
, m_this_delta(src.m_this_delta)
{ }
delegate_mfp(const delegate_mfp &src) = default;
// construct from any member function pointer
template <typename MemberFunctionType, class MemberFunctionClass, typename ReturnType, typename StaticFunctionType>
delegate_mfp(MemberFunctionType mfp, MemberFunctionClass *, ReturnType *, StaticFunctionType)
{
assert(sizeof(mfp) == sizeof(*this));
static_assert(sizeof(mfp) == sizeof(*this), "Unsupported member function pointer size");
*reinterpret_cast<MemberFunctionType *>(this) = mfp;
}
// comparison helpers
bool operator==(const delegate_mfp &rhs) const { return (m_function == rhs.m_function && m_this_delta == rhs.m_this_delta); }
bool isnull() const { return (m_function == 0 && m_this_delta == 0); }
bool operator==(const delegate_mfp &rhs) const { return (m_function == rhs.m_function) && (m_this_delta == rhs.m_this_delta); }
bool isnull() const { return !m_function && !m_this_delta; }
// getters
static delegate_generic_class *real_object(delegate_generic_class *original) { return original; }
@ -330,45 +296,41 @@ private:
delegate_generic_function convert_to_generic(delegate_generic_class *&object) const;
// actual state
uintptr_t m_function; // first item can be one of two things:
uintptr_t m_function = 0; // first item can be one of two things:
// if even, it's a pointer to the function
// if odd, it's the byte offset into the vtable
int m_this_delta; // delta to apply to the 'this' pointer
int m_this_delta = 0; // delta to apply to the 'this' pointer
};
#elif (USE_DELEGATE_TYPE == DELEGATE_TYPE_MSVC)
// ======================> delegate_mfp
const int SINGLE_MEMFUNCPTR_SIZE = sizeof(void (delegate_generic_class::*)());
// struct describing the contents of a member function pointer
class delegate_mfp
{
struct single_base_equiv { delegate_generic_function p; };
struct multi_base_equiv { delegate_generic_function p; int o; };
public:
// default constructor
delegate_mfp()
: m_function(0), m_this_delta(0), m_dummy1(0), m_dummy2(0), m_size(0)
{
}
delegate_mfp() = default;
// copy constructor
delegate_mfp(const delegate_mfp &src)
: m_function(src.m_function), m_this_delta(src.m_this_delta), m_dummy1(src.m_dummy1), m_dummy2(src.m_dummy2), m_size(src.m_size)
{
}
delegate_mfp(const delegate_mfp &src) = default;
// construct from any member function pointer
template <typename MemberFunctionType, class MemberFunctionClass, typename ReturnType, typename StaticFunctionType>
delegate_mfp(MemberFunctionType mfp, MemberFunctionClass *, ReturnType *, StaticFunctionType)
{
static_assert(sizeof(mfp) == sizeof(void *) || sizeof(mfp) == 2 * sizeof(void *));
static_assert((sizeof(mfp) == sizeof(single_base_equiv)) || (sizeof(mfp) == sizeof(multi_base_equiv)), "Unsupported member function pointer size");
m_size = sizeof(mfp);
*reinterpret_cast<MemberFunctionType *>(this) = mfp;
}
// comparison helpers
bool operator==(const delegate_mfp &rhs) const { return (m_function == rhs.m_function); }
bool isnull() const { return (m_function == 0); }
bool operator==(const delegate_mfp &rhs) const { return m_function == rhs.m_function; }
bool isnull() const { return !m_function; }
// getters
static delegate_generic_class *real_object(delegate_generic_class *original) { return original; }
@ -378,28 +340,50 @@ public:
void update_after_bind(FunctionType &funcptr, delegate_generic_class *&object)
{
funcptr = reinterpret_cast<FunctionType>(m_function);
if (m_size > SINGLE_MEMFUNCPTR_SIZE)
if (m_size > sizeof(single_base_equiv))
object = reinterpret_cast<delegate_generic_class *>(reinterpret_cast<std::uint8_t *>(object) + m_this_delta);
}
private:
// extract the generic function and adjust the object pointer
delegate_generic_function convert_to_generic(delegate_generic_class *&object) const;
// actual state
uintptr_t m_function; // first item can be one of two things:
// if even, it's a pointer to the function
// if odd, it's the byte offset into the vtable
int m_this_delta; // delta to apply to the 'this' pointer
uintptr_t m_function = 0; // pointer to function or non-virtual thunk for vtable index
int m_this_delta = 0; // delta to apply to the 'this' pointer for multiple inheritance
int m_dummy1;
int m_dummy2;
int m_dummy1 = 0;
int m_dummy2 = 0;
int m_size;
unsigned m_size = 0;
};
#endif
} // namespace util::detail
// ======================> delegate_late_bind
// simple polymorphic class that must be mixed into any object that is late-bound
class delegate_late_bind
{
public:
delegate_late_bind() { }
virtual ~delegate_late_bind() { }
};
// ======================> binding_type_exception
// exception that is thrown when a bind fails the dynamic_cast
class binding_type_exception : public std::exception
{
public:
binding_type_exception(const std::type_info &target_type, const std::type_info &actual_type)
: m_target_type(target_type), m_actual_type(actual_type)
{ }
const std::type_info &m_target_type;
const std::type_info &m_actual_type;
};
//**************************************************************************
@ -414,22 +398,16 @@ class delegate_base
{
public:
// define our traits
template <class FunctionClass> using traits = delegate_traits<FunctionClass, ReturnType, Params...>;
using generic_static_func = typename traits<delegate_generic_class>::static_func_type;
template <class FunctionClass> using traits = util::detail::delegate_traits<FunctionClass, ReturnType, Params...>;
using generic_static_func = typename traits<util::detail::delegate_generic_class>::static_func_type;
typedef MEMBER_ABI generic_static_func generic_member_func;
// generic constructor
delegate_base()
: m_function(nullptr)
, m_object(nullptr)
, m_latebinder(nullptr)
, m_raw_function(nullptr)
{ }
delegate_base() = default;
// copy constructor
delegate_base(const delegate_base &src)
: m_function(src.m_function)
, m_object(nullptr)
, m_latebinder(src.m_latebinder)
, m_raw_function(src.m_raw_function)
, m_raw_mfp(src.m_raw_mfp)
@ -440,7 +418,6 @@ public:
// copy constructor with late bind
delegate_base(const delegate_base &src, delegate_late_bind &object)
: m_function(src.m_function)
, m_object(nullptr)
, m_latebinder(src.m_latebinder)
, m_raw_function(src.m_raw_function)
, m_raw_mfp(src.m_raw_mfp)
@ -451,35 +428,28 @@ public:
// construct from member function with object pointer
template <class FunctionClass>
delegate_base(typename traits<FunctionClass>::member_func_type funcptr, FunctionClass *object)
: m_function(nullptr)
, m_object(nullptr)
, m_latebinder(&late_bind_helper<FunctionClass>)
, m_raw_function(nullptr)
: m_latebinder(&late_bind_helper<FunctionClass>)
, m_raw_mfp(funcptr, object, static_cast<ReturnType *>(nullptr), static_cast<generic_static_func>(nullptr))
{
bind(reinterpret_cast<delegate_generic_class *>(object));
bind(reinterpret_cast<util::detail::delegate_generic_class *>(object));
}
template <class FunctionClass>
delegate_base(typename traits<FunctionClass>::const_member_func_type funcptr, FunctionClass *object)
: m_function(nullptr)
, m_object(nullptr)
, m_latebinder(&late_bind_helper<FunctionClass>)
, m_raw_function(nullptr)
: m_latebinder(&late_bind_helper<FunctionClass>)
, m_raw_mfp(funcptr, object, static_cast<ReturnType *>(nullptr), static_cast<generic_static_func>(nullptr))
{
bind(reinterpret_cast<delegate_generic_class *>(object));
bind(reinterpret_cast<util::detail::delegate_generic_class *>(object));
}
// construct from static reference function with object reference
template <class FunctionClass>
delegate_base(typename traits<FunctionClass>::static_ref_func_type funcptr, FunctionClass *object)
: m_function(reinterpret_cast<generic_static_func>(funcptr))
, m_object(nullptr)
, m_latebinder(&late_bind_helper<FunctionClass>)
, m_raw_function(reinterpret_cast<generic_static_func>(funcptr))
{
bind(reinterpret_cast<delegate_generic_class *>(object));
bind(reinterpret_cast<util::detail::delegate_generic_class *>(object));
}
// copy operator
@ -501,7 +471,7 @@ public:
// comparison helper
bool operator==(const delegate_base &rhs) const
{
return (m_raw_function == rhs.m_raw_function && object() == rhs.object() && m_raw_mfp == rhs.m_raw_mfp);
return (m_raw_function == rhs.m_raw_function) && (object() == rhs.object()) && (m_raw_mfp == rhs.m_raw_mfp);
}
@ -509,55 +479,59 @@ public:
ReturnType operator()(Params... args) const
{
if (is_mfp() && (HAS_DIFFERENT_ABI))
return (*reinterpret_cast<generic_member_func>(m_function)) (m_object, std::forward<Params>(args)...);
return (*reinterpret_cast<generic_member_func>(m_function))(m_object, std::forward<Params>(args)...);
else
return (*m_function) (m_object, std::forward<Params>(args)...);
return (*m_function)(m_object, std::forward<Params>(args)...);
}
// getters
bool has_object() const { return (object() != nullptr); }
bool has_object() const { return object() != nullptr; }
// helpers
bool isnull() const { return (m_raw_function == nullptr && m_raw_mfp.isnull()); }
bool isnull() const { return !m_raw_function && m_raw_mfp.isnull(); }
bool is_mfp() const { return !m_raw_mfp.isnull(); }
// late binding
void late_bind(delegate_late_bind &object) { if(m_latebinder) bind((*m_latebinder)(object)); }
void late_bind(delegate_late_bind &object)
{
if (m_latebinder)
bind((*m_latebinder)(object));
}
protected:
// return the actual object (not the one we use for calling)
delegate_generic_class *object() const { return is_mfp() ? m_raw_mfp.real_object(m_object) : m_object; }
util::detail::delegate_generic_class *object() const { return is_mfp() ? m_raw_mfp.real_object(m_object) : m_object; }
// late binding function
using late_bind_func = delegate_generic_class*(*)(delegate_late_bind &object);
using late_bind_func = util::detail::delegate_generic_class*(*)(delegate_late_bind &object);
// late binding helper
template <class FunctionClass>
static delegate_generic_class *late_bind_helper(delegate_late_bind &object)
static util::detail::delegate_generic_class *late_bind_helper(delegate_late_bind &object)
{
FunctionClass *result = dynamic_cast<FunctionClass *>(&object);
if (!result)
throw binding_type_exception(typeid(FunctionClass), typeid(object));
return reinterpret_cast<delegate_generic_class *>(result);
return reinterpret_cast<util::detail::delegate_generic_class *>(result);
}
// bind the actual object
void bind(delegate_generic_class *object)
void bind(util::detail::delegate_generic_class *object)
{
m_object = object;
// if we're wrapping a member function pointer, handle special stuff
if (m_object != nullptr && is_mfp())
if (m_object && is_mfp())
m_raw_mfp.update_after_bind(m_function, m_object);
}
// internal state
generic_static_func m_function; // resolved static function pointer
delegate_generic_class * m_object; // resolved object to the post-cast object
late_bind_func m_latebinder; // late binding helper
generic_static_func m_raw_function; // raw static function pointer
delegate_mfp m_raw_mfp; // raw member function pointer
generic_static_func m_function = nullptr; // resolved static function pointer
util::detail::delegate_generic_class * m_object = nullptr; // resolved object to the post-cast object
late_bind_func m_latebinder = nullptr; // late binding helper
generic_static_func m_raw_function = nullptr; // raw static function pointer
util::detail::delegate_mfp m_raw_mfp; // raw member function pointer
};
@ -576,35 +550,47 @@ private:
using basetype = delegate_base<ReturnType, Params...>;
using functoid_setter = void (*)(delegate &);
template <typename T> struct functoid_type_unwrap { using type = std::remove_reference_t<T>; };
template <typename T> struct functoid_type_unwrap<std::reference_wrapper<T> > { using type = typename functoid_type_unwrap<T>::type; };
template <typename T> using unwrapped_functoid_t = typename functoid_type_unwrap<std::remove_cv_t<std::remove_reference_t<T> > >::type;
template <typename T> static constexpr bool matching_non_const_call(T &&) { return false; }
template <typename T> static constexpr bool matching_non_const_call(ReturnType (T::*)(Params...)) { return true; }
template <typename T> static constexpr bool matching_const_call(T &&) { return false; }
template <typename T> static constexpr bool matching_const_call(ReturnType (T::*)(Params...) const) { return true; }
template <typename T> static T *unwrap_functoid(T *functoid) { return functoid; }
template <typename T> static T *unwrap_functoid(std::reference_wrapper<T> *functoid) { return &functoid->get(); }
template <typename T>
unwrapped_functoid_t<T> *unwrap_functoid() noexcept
{
return unwrap_functoid(std::any_cast<std::remove_cv_t<std::remove_reference_t<T> > >(&m_functoid));
}
template <typename T>
static functoid_setter make_functoid_setter()
{
using unwrapped_type = std::remove_cv_t<std::remove_reference_t<T> >;
if constexpr (matching_non_const_call(&unwrapped_type::operator()))
if constexpr (matching_non_const_call(&unwrapped_functoid_t<T>::operator()))
{
return
[] (delegate &obj)
{
obj.basetype::operator=(
basetype(
static_cast<ReturnType (unwrapped_type::*)(Params...)>(&unwrapped_type::operator()),
std::any_cast<unwrapped_type>(&obj.m_functoid)));
static_cast<ReturnType (unwrapped_functoid_t<T>::*)(Params...)>(&unwrapped_functoid_t<T>::operator()),
obj.unwrap_functoid<T>()));
};
}
else if constexpr (matching_const_call(&unwrapped_type::operator()))
else if constexpr (matching_const_call(&unwrapped_functoid_t<T>::operator()))
{
return
[] (delegate &obj)
{
obj.basetype::operator=(
basetype(
static_cast<ReturnType (unwrapped_type::*)(Params...) const>(&unwrapped_type::operator()),
std::any_cast<unwrapped_type>(&obj.m_functoid)));
static_cast<ReturnType (unwrapped_functoid_t<T>::*)(Params...) const>(&unwrapped_functoid_t<T>::operator()),
obj.unwrap_functoid<T>()));
};
}
else
@ -614,8 +600,8 @@ private:
{
obj.basetype::operator=(
basetype(
[] (unwrapped_type &f, Params... args) { return ReturnType(f(std::forward<Params>(args)...)); },
std::any_cast<unwrapped_type>(&obj.m_functoid)));
[] (unwrapped_functoid_t<T> &f, Params... args) { return ReturnType(f(std::forward<Params>(args)...)); },
obj.unwrap_functoid<T>()));
};
}
}
@ -629,7 +615,7 @@ protected:
template <class FunctionClass> using const_member_func_type = typename traits<FunctionClass>::const_member_func_type;
template <class FunctionClass> using static_ref_func_type = typename traits<FunctionClass>::static_ref_func_type;
template <typename T> using suitable_functoid = std::is_convertible<std::invoke_result_t<T, Params...>, ReturnType>;
template <typename T> using suitable_functoid = std::bool_constant<std::is_convertible_v<std::invoke_result_t<T, Params...>, ReturnType> || std::is_same_v<std::void_t<std::invoke_result_t<T, Params...> >, ReturnType> >;
public:
delegate() : basetype() { }

View File

@ -132,6 +132,7 @@
- Inappropriate type for parameterised width/precision
- Positional width/precision specifier not terminated with $
- Inappropriate type for n conversion
- Default conversion for type that lacks stream out operator
Some limitations have been described in passing. Major limitations
and bugs include:
@ -589,42 +590,30 @@ private:
template <typename U>
using unsigned_integer_semantics = std::bool_constant<std::is_integral_v<U> && !std::is_signed_v<U> >;
static void apply_signed(Stream &str, char16_t const &value)
{
str << std::make_signed_t<std::uint_least16_t>(std::uint_least16_t(value));
}
static void apply_signed(Stream &str, char32_t const &value)
{
str << std::make_signed_t<std::uint_least32_t>(std::uint_least32_t(value));
}
template <typename U>
static std::enable_if_t<std::is_same_v<std::make_signed_t<U>, std::make_signed_t<char> > || std::is_integral_v<U> > apply_signed(Stream &str, U const &value)
{
if constexpr (std::is_same_v<std::make_signed_t<U>, std::make_signed_t<char> >)
str << int(std::make_signed_t<U>(value));
else if constexpr (std::is_signed_v<U>)
else if constexpr (!std::is_signed_v<U> || std::is_same_v<typename Stream::char_type, U>)
str << std::make_signed_t<U>(value);
else if constexpr (std::is_invocable_v<decltype([] (auto &x, auto &y) -> decltype(x << y) { return x << y; }), Stream &, U const &>)
str << value;
else
str << std::make_signed_t<U>(value);
}
static void apply_unsigned(Stream &str, char16_t const &value)
{
str << std::uint_least16_t(value);
}
static void apply_unsigned(Stream &str, char32_t const &value)
{
str << std::uint_least32_t(value);
}
template <typename U>
static std::enable_if_t<std::is_same_v<std::make_unsigned_t<U>, std::make_unsigned_t<char> > || std::is_integral_v<U> > apply_unsigned(Stream &str, U const &value)
{
if constexpr (std::is_same_v<std::make_unsigned_t<U>, std::make_unsigned_t<char> >)
str << unsigned(std::make_unsigned_t<U>(value));
else if constexpr (std::is_signed_v<U>)
else if constexpr (!std::is_unsigned_v<U> || std::is_same_v<typename Stream::char_type, U>)
str << std::make_unsigned_t<U>(value);
else
else if constexpr (std::is_invocable_v<decltype([] (auto &x, auto &y) -> decltype(x << y) { return x << y; }), Stream &, U const &>)
str << value;
else
str << std::make_unsigned_t<U>(value);
}
public:
@ -830,7 +819,15 @@ public:
str << reinterpret_cast<void const *>(std::uintptr_t(value));
break;
default:
str << value;
if constexpr (std::is_invocable_v<decltype([] (auto &x, auto &y) -> decltype(x << y) { return x << y; }), Stream &, U const &>)
{
str << value;
}
else
{
assert(false); // stream out operator not declared or declared deleted
str << '?';
}
}
}
else