From c6fe38989c1a8c74b44ee38c9d795a3ffda13d15 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Mon, 23 Nov 2020 17:56:35 +0300 Subject: [PATCH] util::function bugfixes & strong exception guarantee --- libs/util/include/psemek/util/function.hpp | 77 +++++++++++++++++----- 1 file changed, 60 insertions(+), 17 deletions(-) diff --git a/libs/util/include/psemek/util/function.hpp b/libs/util/include/psemek/util/function.hpp index 666f0539..14d9b523 100644 --- a/libs/util/include/psemek/util/function.hpp +++ b/libs/util/include/psemek/util/function.hpp @@ -20,6 +20,22 @@ namespace psemek::util template struct function { + private: + static constexpr std::size_t storage_size = sizeof(void*) * 3; + static constexpr std::size_t storage_align = alignof(void*); + + template + struct uses_static_storage + : std::bool_constant + > + {}; + + public: + using signature = R(Args...); function() noexcept = default; @@ -33,8 +49,18 @@ namespace psemek::util function (function const &) = delete; function & operator = (function const &) = delete; + ~function(); + + // operator = (F && f) has strong exception guarantree: + // if the assignment throws, the function remains unchanged + template - function & operator = (F && f); + std::enable_if_t>::value, function &> + operator = (F && f); + + template + std::enable_if_t>::value, function &> + operator = (F && f); explicit operator bool() const { @@ -46,10 +72,9 @@ namespace psemek::util void reset(); - private: - static constexpr std::size_t storage_size = sizeof(void*) * 3; - static constexpr std::size_t storage_align = alignof(void*); + void swap(function & other) noexcept; + private: std::aligned_storage_t storage_; struct vtable @@ -78,18 +103,29 @@ namespace psemek::util template template - function & function::operator = (F && f) + std::enable_if_t::template uses_static_storage>::value, function &> + function::operator = (F && f) { reset(); assign(std::forward(f)); return *this; } + template + template + std::enable_if_t::template uses_static_storage>::value, function &> + function::operator = (F && f) + { + function(std::forward(f)).swap(*this); + return *this; + } + template function::function (function && other) noexcept { vtable_ = other.vtable_; - vtable_->move(&other.storage_, &storage_); + if (vtable_) + vtable_->move(&other.storage_, &storage_); other.reset(); } @@ -99,12 +135,20 @@ namespace psemek::util if (this == &other) return *this; + reset(); vtable_ = other.vtable_; - vtable_->move(&other.storage_, &storage_); + if (vtable_) + vtable_->move(&other.storage_, &storage_); other.reset(); return *this; } + template + function::~function() + { + reset(); + } + template template R function::operator()(Args1 && ... args) const @@ -124,27 +168,26 @@ namespace psemek::util vtable_ = nullptr; } + template + void function::swap(function & other) noexcept + { + std::swap(*this, other); + } + template template void function::assign(F && f) { using T = std::decay_t; - constexpr bool static_storage = true - && sizeof(T) <= storage_size - && alignof(T) <= storage_align - && ((storage_align % alignof(T)) == 0) - && std::is_nothrow_move_constructible_v - ; - - if constexpr (static_storage) + if constexpr (uses_static_storage::value) { new (reinterpret_cast(&storage_)) T(std::move(f)); static vtable m = { [](void * src, void * dst){ new (reinterpret_cast(dst)) T(std::move(*reinterpret_cast(src))); }, [](void * src){ reinterpret_cast(src)->~T(); }, - [](void * src, Args ... args) -> R { return (*reinterpret_cast(src))(static_cast(args)...); } + [](void * src, Args ... args) -> R { return std::invoke(*reinterpret_cast(src), static_cast(args)...); } }; vtable_ = &m; @@ -156,7 +199,7 @@ namespace psemek::util static vtable m = { [](void * src, void * dst){ *reinterpret_cast(dst) = *reinterpret_cast(src); *reinterpret_cast(src) = nullptr; }, [](void * src){ delete *reinterpret_cast(src); }, - [](void * src, Args ... args) -> R { return (**reinterpret_cast(src))(static_cast(args)...); } + [](void * src, Args ... args) -> R { return std::invoke(**reinterpret_cast(src), static_cast(args)...); } }; vtable_ = &m;