/* ***************************************************************************** * kblib is a general utility library for C++14 and C++17, intended to provide * performant high-level abstractions and more expressive ways to do simple * things. * * Copyright (c) 2021 killerbee * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ****************************************************************************/ /** * @file * @brief Provides poly_obj, which enables polymorphism to be used without * unnecessary per-object dynamic allocations. * * @author killerbee * @date 2019-2021 * @copyright GNU General Public Licence v3.0 */ #ifndef POLY_OBJ_H #define POLY_OBJ_H #include "hash.h" #include "variant.h" namespace KBLIB_NS { enum class construct_type : unsigned { none = 0, copy_only = 1, move = 2, both = 3, throw_move = 4, both_throw = 5, }; KBLIB_NODISCARD constexpr auto operator|(construct_type l, construct_type r) noexcept -> construct_type { return static_cast(etoi(l) | etoi(r)); } KBLIB_NODISCARD constexpr auto operator&(construct_type l, construct_type r) noexcept -> construct_type { return static_cast(etoi(l) & etoi(r)); } KBLIB_NODISCARD constexpr auto operator*(construct_type l, bool r) noexcept -> construct_type { return r ? l : construct_type::none; } /** * @namespace kblib::detail_poly * @brief Implementation details for poly_obj. * @internal */ namespace detail_poly { KBLIB_NODISCARD constexpr auto copyable(construct_type type) noexcept -> bool { return (type & construct_type::copy_only) != construct_type::none; } KBLIB_NODISCARD constexpr auto movable(construct_type type) noexcept -> bool { return (type & construct_type::move) != construct_type::none; } KBLIB_NODISCARD constexpr auto nothrow_movable(construct_type type) noexcept -> bool { return (type & construct_type::move) != construct_type::none and (type & construct_type::throw_move) == construct_type::none; } KBLIB_NODISCARD constexpr auto make_ctype(bool copyable, bool movable, bool nothrow_movable) -> construct_type { if (copyable) { if (not movable) { return construct_type::copy_only; } else if (nothrow_movable) { return construct_type::both; } else { return construct_type::both_throw; } } else if (movable) { return construct_type::move; } else { return construct_type::none; } } template struct construct_conditional; template <> struct construct_conditional { construct_conditional() noexcept = default; construct_conditional(const construct_conditional&) = delete; construct_conditional(construct_conditional&&) = delete; auto operator=(const construct_conditional&) -> construct_conditional& = default; auto operator=(construct_conditional&&) -> construct_conditional& = default; ~construct_conditional() = default; }; template <> struct construct_conditional { construct_conditional() noexcept = default; construct_conditional(const construct_conditional&) noexcept = default; construct_conditional(construct_conditional&&) = delete; auto operator=(const construct_conditional&) -> construct_conditional& = default; auto operator=(construct_conditional&&) -> construct_conditional& = default; ~construct_conditional() = default; }; template <> struct construct_conditional { construct_conditional() noexcept = default; construct_conditional(const construct_conditional&) noexcept = delete; construct_conditional(construct_conditional&&) noexcept = default; auto operator=(const construct_conditional&) -> construct_conditional& = default; auto operator=(construct_conditional&&) -> construct_conditional& = default; ~construct_conditional() = default; }; template <> struct construct_conditional {}; template <> struct construct_conditional { construct_conditional() noexcept = default; construct_conditional(const construct_conditional&) noexcept = delete; construct_conditional(construct_conditional&&) noexcept(false) {} auto operator=(const construct_conditional&) -> construct_conditional& = default; auto operator=(construct_conditional&&) -> construct_conditional& = default; ~construct_conditional() = default; }; template <> struct construct_conditional { construct_conditional() noexcept = default; construct_conditional(const construct_conditional&) = default; construct_conditional(construct_conditional&&) noexcept(false) {} auto operator=(const construct_conditional&) -> construct_conditional& = default; auto operator=(construct_conditional&&) -> construct_conditional& = default; ~construct_conditional() = default; }; template constexpr construct_type construct_traits = construct_type::copy_only* std::is_copy_constructible::value | construct_type::move* std::is_move_constructible::value | construct_type::throw_move*( std::is_move_constructible::value & not std::is_nothrow_move_constructible::value); template struct erased_hash_t {}; template struct erased_hash_t, T>>> { static auto default_hash(void* obj) -> std::size_t { return std::hash()(*static_cast(obj)); } alias hash = &default_hash; }; template struct kblib_erased_hash_t {}; template struct kblib_erased_hash_t< T, void_t, T>>> { static auto default_hash(void* obj) -> std::size_t { return FNV_hash{}(*static_cast(obj)); } alias hash = &default_hash; }; template struct erased_construct : Traits::copy_t , Traits::move_t , Traits::destroy_t { using Traits::copy_t::copy; using Traits::move_t::move; using Traits::destroy_t::destroy; }; template KBLIB_NODISCARD auto make_ops_t() noexcept -> erased_construct { static_assert(implies::value>::value, "T must be copy constructible if Traits is."); static_assert( implies::value>::value, "T must be nothrow move constructible if Traits is."); static_assert( implies::value>::value, "T must be move constructible if Traits is."); return {{typename Traits::copy_t{static_cast(nullptr)}}, {typename Traits::move_t{static_cast(nullptr)}}, {typename Traits::destroy_t{static_cast(nullptr)}}}; } template struct extract_derived_size : std::integral_constant {}; template struct extract_derived_size sizeof(T))>> : std::integral_constant {}; } // namespace detail_poly /// Does nothing; matches the copy construction signature. template inline auto noop(void*, const T*) -> T* { return nullptr; } /// Does nothing; matches the move construction signature. template inline auto noop(void*, T*) noexcept(nothrow) -> T* { return nullptr; } /// Implements type erasure for copy construction. template struct default_copy { private: template static auto do_copy(void* dest, const Obj* from) -> Obj* { return static_cast(new (dest) T(*static_cast(from))); } auto (*p_copy)(void* dest, const Obj* from) -> Obj* = &noop; public: /// Constructs an object which does nothing. default_copy() noexcept = default; /// Constructs an object which can copy a T. template explicit default_copy(T*) noexcept : p_copy(&do_copy) {} /// Copies an object of previously-established type. KBLIB_NODISCARD auto copy(void* dest, const Obj* from) -> Obj* { return p_copy(dest, from); } }; template struct default_copy { default_copy() noexcept = default; template explicit default_copy(T*) noexcept {} auto copy(void* dest, const Obj* from) -> Obj* = delete; }; /** * @brief Implements copy construction using a virtual clone method. * This type is provided mostly as an example. */ template Obj*> struct clone_copy { /// Does nothing clone_copy() = default; /// Does nothing template clone_copy(T*) {} /// Invokes the clone method to copy the object KBLIB_NODISCARD auto copy(void* dest, const Obj* from) -> Obj* { return (from->*clone)(dest); } }; /// Implements type erasure for move construction. template struct default_move { private: template static auto do_move(void* dest, Obj* from) noexcept(nothrow) -> Obj* { return static_cast(new (dest) T(std::move(*static_cast(from)))); } // noexcept isn't working here on clang auto (*p_move)(void* dest, Obj* from) noexcept(false) -> Obj* = &noop; public: /// Constructs an object which does nothing. default_move() noexcept = default; /// Constructs an object which can move a T. template explicit default_move(T*) noexcept : p_move(&do_move) {} /// Moves an object of previously-established type. KBLIB_NODISCARD auto move(void* dest, Obj* from) noexcept(nothrow) -> Obj* { return p_move(dest, from); } }; template struct default_move { default_move() noexcept = default; template explicit default_move(T*) noexcept {} auto move(void* dest, Obj* from) noexcept(nothrow) -> Obj* = delete; }; // fallback on copy template struct default_move : private default_copy { using default_copy::default_copy; KBLIB_NODISCARD auto move(void* dest, const Obj* from) noexcept(nothrow) -> Obj* { return this->copy(dest, from); } }; /** * @brief Uses the class's virtual destructor. */ template struct default_destroy { default_destroy() noexcept = default; template explicit default_destroy(T*) noexcept {} auto destroy(Obj* obj) -> void { obj->~Obj(); } static_assert(std::has_virtual_destructor::value, "Obj must have a virtual destructor"); }; // TODO(killerbee13): Distinguish between pointers to T and to // most-derived-object /** * @brief poly_obj_traits is a traits class template which abstracts the allowed * operations on a polymorphic type hierarchy. Any operation allowed by the * traits must be usable for the entire hierarchy, not just the base class. * * Users of poly_obj may provide explicit specializations of this type, as long * as they satisfy all the requirements listed here. Alternatively, users may * write their own traits types, subject to the same restrictions. */ template > struct poly_obj_traits { /** * @brief The default capacity to use if not overridden. * * The default implementation uses Obj::max_derived_size if it exists, or * sizeof(Obj) otherwise. */ constexpr static std::size_t default_capacity = detail_poly::extract_derived_size::value; /** * @brief How much to align the storage by. */ constexpr static std::size_t alignment = std::max(alignof(Obj), alignof(std::max_align_t)); /** * @brief If the object is copy constructible. */ constexpr static bool copyable = detail_poly::copyable(CType); /** * @brief If the object is move constructible */ constexpr static bool movable = detail_poly::movable(CType); /** * @brief If the object is nothrow move constructible */ constexpr static bool nothrow_movable = detail_poly::nothrow_movable(CType); /** * @brief Implements type erasure for copy construction. * * Must be assignable. * If copying is enabled, copy_t must have: * - a nothrow default constructor. * - a nothrow constructor template taking a single parameter of type T* * (always nullptr) that produces a copy constructor for type T. * - a member function 'copy' which performs the actual copying. * * @see kblib::default_copy */ using copy_t = default_copy; /** * @brief Implements type erasure for move construction. * * Must be assignable. * If moving is enabled, move_t must have: * - a nothrow default constructor. * - a nothrow constructor template taking a single parameter of type T* * (always nullptr) that produces a move constructor for type T. * - a member function 'move' which performs the actual moving. * * @note Move may fall back on copy. * @see kblib::default_move */ using move_t = default_move; /** * @brief Implements type erasure for destruction. The default implementation * requires and always uses virtual dispatch for destruction. * * Must be assignable. * Must have: * - a nothrow default constructor. * - a nothrow constructor template taking a single parameter of type T* * (always nullptr) that produces a destroyer for type T. * - a member function 'destroy' which performs the actual destruction. * * @see kblib::default_destroy */ using destroy_t = default_destroy; static_assert(std::is_empty::value, ""); }; template using move_only_traits = poly_obj_traits; template using no_move_traits = poly_obj_traits; /** * @brief Inline polymorphic object. Generally mimics the interfaces of * std::optional and std::variant. * * Provides dynamic polymorphism without dynamic allocation. By default, it is * copy and move constructible if and only if Obj is. The storage capacity can * be overloaded in case derived objects are larger than the base (this is * expected to be commonplace). * * @tparam Obj The base class type which the poly_obj will store. Must have a * virtual destructor unless a custom traits class is provided. * @tparam Capacity The inline capacity allocated for the contained object. May * be set larger than sizeof(Obj) to account for larger derived classes. A value * of 0 indicates that the traits type should be used to obtain the capacity. * @tparam Traits A type providing member types and constants defining the * allowed operations on the object type. * * @see kblib::poly_obj_traits * @nosubgrouping */ template > class poly_obj : private detail_poly::construct_conditional , private detail_poly::erased_construct { private: using disabler = detail_poly::construct_conditional; using ops_t = detail_poly::erased_construct; public: /** * @brief Equal to Capacity if specified, else Traits::default_capacity. */ static constexpr std::size_t capacity = Capacity > 0 ? Capacity : Traits::default_capacity; static_assert(capacity >= sizeof(Obj), "Capacity must be large enough for the object type."); static_assert(not std::is_array::value, "poly_obj of array type is disallowed."); using base_type = Obj; using traits_type = Traits; /** * @name Construction */ ///@{ /** * @brief The default constructor does not construct any contained object. */ constexpr poly_obj() = default; /** * @brief Explicitly do not construct an object. */ constexpr poly_obj(std::nullptr_t) noexcept : poly_obj() {} /** * @brief Copy-constructs the contained object from obj. * * This function can only be called if Obj is copy-constructible. * * @param obj The object to copy. */ constexpr poly_obj(const Obj& obj) : ptr(new (data) Obj(obj)) {} /** * @brief Move-constructs the contained object from obj. * * This function can only be called if Obj is move-constructible. * * @param obj The object to move from. */ constexpr poly_obj(Obj&& obj) noexcept(Traits::nothrow_movable) : ptr(new (data) Obj(std::move(obj))) {} /** * @brief Constructs the contained object in-place without copying or moving. * * @param args Arguments to be passed to the constructor of Obj. * @see kblib::fakestd::in_place */ template ::value, int> = 0> constexpr explicit poly_obj(fakestd::in_place_t, Args&&... args) noexcept( std::is_nothrow_constructible::value) : ops_t(detail_poly::make_ops_t()) , ptr(new (data) Obj(std::forward(args)...)) {} /** * @brief Constructs the contained object in-place without copying or moving. * * @param args Arguments to be passed to the constructor of Obj. * @see kblib::in_place_agg */ template ::value, int> = 0> constexpr explicit poly_obj(kblib::in_place_agg_t, Args&&... args) noexcept( std::is_nothrow_constructible::value) : ops_t(detail_poly::make_ops_t()) , ptr(new (data) Obj{std::forward(args)...}) {} /** * @brief Constructs a poly_obj containing an object of derived type. * * This function provides the polymorphism of poly_obj. * * sizeof(U) must be less than or equal to capacity, and must be * copy-constructible and move-constructible if Traits is. * * @tparam U A type publically derived from Obj. * @param args Arguments to pass to the constructor of U. * @return poly_obj A poly_obj containing an object of type U. */ template KBLIB_NODISCARD static auto make(Args&&... args) noexcept( std::is_nothrow_constructible::value) -> poly_obj { static_assert(sizeof(U) <= capacity, "U must fit inside of the inline capacity."); static_assert(alignof(U) <= Traits::alignment, "U must be no more aligned than Traits::alignment"); static_assert(std::is_base_of::value and std::is_convertible::value, "Obj must be an accessible base of Obj."); static_assert( implies::value>::value, "U must be copy constructible if Traits::copyable is true."); static_assert( implies::value>::value, "U must be move constructible if Traits::movable is true."); return {tag{}, std::forward(args)...}; } /** * @brief Constructs a poly_obj containing an object of derived type. * * This function provides the polymorphism of poly_obj. * * sizeof(U) must be less than or equal to capacity, and must be * copy-constructible and move-constructible if Traits is. * * @tparam U A type publically derived from Obj. * @param args Arguments to pass to the constructor of U. * @return poly_obj A poly_obj containing an object of type U. */ template KBLIB_NODISCARD static auto make_aggregate(Args&&... args) noexcept( std::is_nothrow_constructible::value) -> poly_obj { static_assert(sizeof(U) <= capacity, "U must fit inside of the inline capacity."); static_assert(alignof(U) <= Traits::alignment, "U must be no more aligned than Traits::alignment"); static_assert(std::is_base_of::value and std::is_convertible::value, "Obj must be an accessible base of Obj."); static_assert( implies::value>::value, "U must be copy constructible if Traits::copyable is true."); static_assert( implies::value>::value, "U must be move constructible if Traits::movable is true."); return {tag{}, std::forward(args)...}; } ///@} /** * @name Copy/move operators */ ///@{ /** * @brief Constructs a copy of other. * * This function can only be called if Traits::copyable is true. * * @param other A poly_obj to copy from. */ constexpr poly_obj(const poly_obj& other) : disabler(other) , ops_t(other) { if (other.ptr) { ptr = this->copy(data, other.ptr); } } /** * @brief Moves the contained object of other into this. Note that the moved- * from poly_obj is not cleared; instead, its contained value is moved from. * * This function can only be called if Traits::movable is true. * * @param other A poly_obj to move from. */ constexpr poly_obj(poly_obj&& other) noexcept(Traits::nothrow_movable) : disabler(std::move(other)) , ops_t(std::move(other)) { if (other.ptr) { ptr = this->move(data, other.ptr); } } /** * @brief Destroys the contained object, if any, and then copies other as in * the copy constructor. * * @param other A poly_obj to copy from. * @return poly_obj& *this. * * @exceptions In the event that the constructor of Obj throws, the poly_obj * is cleared and the exception rethrown. */ auto operator=(const poly_obj& other) & -> poly_obj& { if (this == &other) { return *this; } clear(); static_cast(*this) = other; if (other.ptr) { ptr = this->copy(data, other.ptr); } return *this; } /** * @brief Destroys the contained object, if any, and then moves from other as * in the move constructor. * * @param other A poly_obj to move from. * @return poly_obj& *this. * * @exceptions In the event that the constructor of Obj throws, the poly_obj * is cleared and the exception rethrown. */ auto operator=(poly_obj&& other) & noexcept(Traits::nothrow_movable) -> poly_obj& { if (this == &other) { return *this; } clear(); static_cast(*this) = other; if (other.ptr) { ptr = this->move(data, other.ptr); } return *this; } ///@} /** * @name Validity * @brief Check if the poly_obj contains a value. */ ///@{ KBLIB_NODISCARD auto has_value() const& noexcept -> bool { return ptr; } explicit operator bool() const& noexcept { return has_value(); } ///@} /** * @name Destruction */ ///@{ /** * @brief Empties the poly_obj, reverting to a default-constructed state. */ auto clear() noexcept -> void { if (ptr) { this->destroy(ptr); ptr = nullptr; } static_cast(*this) = {}; } ~poly_obj() noexcept { if (ptr) { this->destroy(ptr); } } ///@} /** * @name Object Access * @brief These functions allow access to the contained value. */ ///@{ /** * @brief Returns a reference to the contained object. * * Returns a reference to the contained object, if it exists. If it does not * exist, the behavior is undefined. Notably, the constness and reference * qualification of *this carries over to the contained object, because it is * contained inside of *this. * * @return Obj& A reference to the contained object. */ KBLIB_NODISCARD auto operator*() & noexcept -> Obj& { return *get(); } /** * @brief Returns a reference to the contained object. * * Returns a reference to the contained object, if it exists. If it does not * exist, the behavior is undefined. Notably, the constness and reference * qualification of *this carries over to the contained object, because it is * contained inside of *this. * * @return const Obj& A reference to the contained object. */ KBLIB_NODISCARD auto operator*() const& noexcept -> const Obj& { return *get(); } /** * @brief Returns a reference to the contained object. * * Returns a reference to the contained object, if it exists. If it does not * exist, the behavior is undefined. Notably, the constness and reference * qualification of *this carries over to the contained object, because it is * contained inside of *this. * * @return Obj&& An rvalue reference to the contained object. */ KBLIB_NODISCARD auto operator*() && noexcept(Traits::nothrow_movable) -> Obj&& { return std::move(*get()); } /** * @brief Returns a reference to the contained object. * * Returns a reference to the contained object, if it exists. If it does not * exist, the behavior is undefined. Notably, the constness and reference * qualification of *this carries over to the contained object, because it is * contained inside of *this. * * This particular overload is not expected to be very useful, but it is * provided for completeness. * * @return const Obj&& A const rvalue reference to the contained object. */ KBLIB_NODISCARD auto operator*() const&& noexcept(Traits::nothrow_movable) -> const Obj&& { return std::move(*get()); } /** * @brief Returns a pointer to the contained object. * * Returns a reference to the contained object, if it exists. If it does not * exist, the behavior is undefined. Notably, the constness of *this carries * over to the contained object, because it is contained inside of *this. * * @return Obj* A pointer to the contained object. */ KBLIB_NODISCARD auto get() & noexcept -> Obj* { return ptr; } /** * @brief Returns a pointer to the contained object. * * Returns a reference to the contained object, if it exists. If it does not * exist, the behavior is undefined. Notably, the constness of *this carries * over to the contained object, because it is contained inside of *this. * * @return const Obj* A pointer to the contained object. */ KBLIB_NODISCARD auto get() const& noexcept -> const Obj* { return ptr; } /** * @brief Returns a pointer to the contained object. * * Returns a reference to the contained object, if it exists. If it does not * exist, the behavior is undefined. Notably, the constness of *this carries * over to the contained object, because it is contained inside of *this. * * @return Obj* A pointer to the contained object. */ KBLIB_NODISCARD auto operator->() & noexcept -> Obj* { return ptr; } /** * @brief Returns a pointer to the contained object. * * Returns a reference to the contained object, if it exists. If it does not * exist, the behavior is undefined. Notably, the constness of *this carries * over to the contained object, because it is contained inside of *this. * * @return const Obj* A pointer to the contained object. */ KBLIB_NODISCARD auto operator->() const& noexcept -> const Obj* { return ptr; } /** * @brief Invokes the contained function object, if Obj is a callable type. * * Invokes the contained object, if it exists. If it does not exist, the * behavior is undefined. * * @param args The arguments to forward to the function. * @return invoke_result_t The return value of * the function. */ template auto operator()(Args&&... args) noexcept( is_nothrow_invocable::value) -> fakestd::invoke_result_t { return kblib::invoke(*get(), std::forward(args)...); } /** * @brief Invokes the contained function object, if Obj is a callable type. * * Invokes the contained object, if it exists. If it does not exist, the * behavior is undefined. * * @param args The arguments to forward to the function. * @return invoke_result_t The return value of * the function. */ template auto operator()(Args&&... args) const noexcept(is_nothrow_invocable::value) -> fakestd::invoke_result_t { return kblib::invoke(*get(), std::forward(args)...); } /** * @brief Access a member variable using a pointer to member */ template enable_if_t::value, member_type>& operator->*(member_type Obj::*member) & noexcept { return get()->*member; } /** * @brief Access a member variable using a pointer to member */ template const enable_if_t< not std::is_member_function_pointer::value, member_type>& operator->*(member_type Obj::*member) const& noexcept { return get()->*member; } /** * @brief Access a member variable using a pointer to member */ template enable_if_t::value, member_type>&& operator->*(member_type Obj::*member) && noexcept { return std::move(get()->*member); } /** * @brief Access a member variable using a pointer to member */ template const enable_if_t< not std::is_member_function_pointer::value, member_type>&& operator->*(member_type Obj::*member) const&& noexcept { return std::move(get()->*member); } /** * @brief Call a member function using a pointer to member * @remark unqualified on & */ template KBLIB_NODISCARD auto operator->*( member_type (Obj::*member)(Args...)) & noexcept { return [member, value = get()](Args... args) -> decltype(auto) { return (value->*member)(std::forward(args)...); }; } /** * @brief Call a member function using a pointer to member * @remark const on & */ template KBLIB_NODISCARD auto operator->*(member_type (Obj::*member)(Args...) const) & noexcept { return [member, value = get()](Args... args) -> decltype(auto) { return (value->*member)(std::forward(args)...); }; } /** * @brief Call a member function using a pointer to member * @remark const on const& */ template KBLIB_NODISCARD auto operator->*(member_type (Obj::*member)(Args...) const) const& noexcept { return [member, value = get()](Args... args) -> decltype(auto) { return (value->*member)(std::forward(args)...); }; } /** * @brief Call a member function using a pointer to member * @remark const on && */ template KBLIB_NODISCARD auto operator->*(member_type (Obj::*member)(Args...) const) && noexcept { return [member, value = get()](Args... args) -> decltype(auto) { return (value->*member)(std::forward(args)...); }; } /** * @brief Call a member function using a pointer to member * @remark & on & */ template KBLIB_NODISCARD auto operator->*( member_type (Obj::*member)(Args...) &) & noexcept { return [member, value = get()](Args... args) -> decltype(auto) { return (value->*member)(std::forward(args)...); }; } /** * @brief Call a member function using a pointer to member * @remark const& on & */ template KBLIB_NODISCARD auto operator->*(member_type (Obj::*member)(Args...) const&) & noexcept { return [member, value = get()](Args... args) -> decltype(auto) { return (value->*member)(std::forward(args)...); }; } /** * @brief Call a member function using a pointer to member * @remark const& on const& */ template KBLIB_NODISCARD auto operator->*(member_type (Obj::*member)(Args...) const&) const& noexcept { return [member, value = get()](Args... args) -> decltype(auto) { return (value->*member)(std::forward(args)...); }; } /** * @brief Call a member function using a pointer to member * @remark && on && */ template KBLIB_NODISCARD auto operator->*(member_type (Obj::*member)(Args...) &&) && noexcept { return [member, value = get()](Args... args) -> decltype(auto) { return (std::move(*value).*member)(std::forward(args)...); }; } /** * @brief Call a member function using a pointer to member * @remark const& on && */ template KBLIB_NODISCARD auto operator->*(member_type (Obj::*member)(Args...) const&) && noexcept { return [member, value = get()](Args... args) -> decltype(auto) { return (value->*member)(std::forward(args)...); }; } /** * @brief Call a member function using a pointer to member * @remark Unqualified on && */ template KBLIB_NODISCARD auto operator->*( member_type (Obj::*member)(Args...)) && noexcept { return [member, value = get()](Args... args) -> decltype(auto) { return (value->*member)(std::forward(args)...); }; } /** * @brief Call a member function using a pointer to member * @remark const&& on && */ template KBLIB_NODISCARD auto operator->*(member_type (Obj::*member)(Args...) const&&) && noexcept { return [member, value = get()](Args... args) -> decltype(auto) { return (std::move(*value).*member)(std::forward(args)...); }; } /** * @brief Call a member function using a pointer to member * @remark const&& on const&& */ template KBLIB_NODISCARD auto operator->*(member_type (Obj::*member)(Args...) const&&) const&& noexcept { return [member, value = get()](Args... args) -> decltype(auto) { return (std::move(*value).*member)(std::forward(args)...); }; } private: alignas(Traits::alignment) byte data[capacity]{}; Obj* ptr{}; template struct tag {}; template poly_obj(tag, Args&&... args) noexcept( std::is_nothrow_constructible::value) : ops_t(detail_poly::make_ops_t()) , ptr(new (data) U(std::forward(args)...)) {} template poly_obj(tag, Args&&... args) noexcept( std::is_nothrow_constructible::value) : ops_t(detail_poly::make_ops_t()) , ptr(new (data) U{std::forward(args)...}) {} }; template constexpr auto poly_warn_if() -> std::nullptr_t { return {}; } template <> [[deprecated( "make_poly_obj(): Specifying capacity with this " "API is error-prone, consider " "using a type alias instead to avoid repetition.")]] constexpr inline auto poly_warn_if() -> std::nullptr_t { return {}; } /** * @brief A convenience factory for making poly_objs. * @relates poly_obj */ template , typename... Args> KBLIB_NODISCARD auto make_poly_obj( Args&&... args, std::nullptr_t = poly_warn_if<(Capacity != sizeof(D) and Capacity > Traits::default_capacity)>()) -> poly_obj { return poly_obj::template make(std::forward(args)...); } } // namespace KBLIB_NS #endif // POLY_OBJ_H