diff options
Diffstat (limited to 'src/lib/corelib/tools/propagate_const.h')
-rw-r--r-- | src/lib/corelib/tools/propagate_const.h | 418 |
1 files changed, 418 insertions, 0 deletions
diff --git a/src/lib/corelib/tools/propagate_const.h b/src/lib/corelib/tools/propagate_const.h new file mode 100644 index 000000000..65e35fc8c --- /dev/null +++ b/src/lib/corelib/tools/propagate_const.h @@ -0,0 +1,418 @@ +/**************************************************************************** +** MIT License +** +** Copyright (C) 2022-2023 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com +** Author: Giuseppe D'Angelo <giuseppe.dangelo@kdab.com> +** +** This file is part of KDToolBox (https://github.com/KDAB/KDToolBox). +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, ** and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice (including the next paragraph) +** shall be included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF ** CONTRACT, TORT OR OTHERWISE, +** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +** DEALINGS IN THE SOFTWARE. +****************************************************************************/ + +#ifndef KDTOOLBOX_PROPAGATE_CONST +#define KDTOOLBOX_PROPAGATE_CONST + +#include <functional> +#include <type_traits> +#include <utility> + +// Our SFINAE is too noisy and clang-format gets confused. +// clang-format off + +namespace KDToolBox +{ + +template <typename T> +class propagate_const; + +namespace detail { +// Type traits to detect propagate_const specializations +template <typename T> +struct is_propagate_const : std::false_type +{ +}; + +template <typename T> +struct is_propagate_const<propagate_const<T>> : std::true_type +{ +}; + +// Using SFINAE in a base class to constrain the conversion operators. +// Otherwise, if we make them function templates and apply SFINAE directly on +// them, we would not be able to do certain conversions (cf. +// [over.ics.user/3]). +template <typename T> +using propagate_const_element_type = std::remove_reference_t<decltype(*std::declval<T &>())>; + +// Const conversion. +// NON-STANDARD: checks that `const T` is convertible, not `T`. +// See https://wg21.link/lwg3812 +template <typename T, + bool = std::disjunction_v< + std::is_pointer<T>, + std::is_convertible<const T, const propagate_const_element_type<T> *> + > + > +struct propagate_const_const_conversion_operator_base +{ +}; + +template <typename T> +struct propagate_const_const_conversion_operator_base<T, true> +{ + constexpr operator const propagate_const_element_type<T> *() const; +}; + +// Non-const conversion +template <typename T, + bool = std::disjunction_v< + std::is_pointer<T>, + std::is_convertible<T, propagate_const_element_type<T> *> + > + > +struct propagate_const_non_const_conversion_operator_base +{ +}; + +template <typename T> +struct propagate_const_non_const_conversion_operator_base<T, true> +{ + constexpr operator propagate_const_element_type<T> *(); +}; + +template <typename T> +struct propagate_const_base + : propagate_const_const_conversion_operator_base<T>, + propagate_const_non_const_conversion_operator_base<T> +{}; + +} // namespace detail + +/* + TODO: This code could benefit from a C++20 overhaul: + * concepts + * three-way comparisons + * explicit(bool) + + However we can't depend on C++20 yet... +*/ + +template <typename T> +class propagate_const : public detail::propagate_const_base<T> +{ +public: + using element_type = detail::propagate_const_element_type<T>; + + // Special member functions + propagate_const() = default; + propagate_const(propagate_const &&) = default; + propagate_const &operator=(propagate_const &&) = default; + propagate_const(const propagate_const &) = delete; + propagate_const &operator=(const propagate_const &) = delete; + ~propagate_const() = default; + + // Constructor from values + + template < + typename U, + std::enable_if_t< // This constructor is enabled if: + std::conjunction_v< + std::is_constructible<T, U>, // 1) we can build a T from a U, + std::negation<detail::is_propagate_const<std::decay_t<U>>>, // 2) we are not making a + // converting constructor, + std::is_convertible<U, T> // 3) and the conversion from U to T is implicit; + >, + bool> = true> + /* implicit */ // then, this constructor is implicit too. + propagate_const(U &&other) + : m_t(std::forward<U>(other)) + { + } + + template < + typename U, + std::enable_if_t< // This constructor is enabled if: + std::conjunction_v< + std::is_constructible<T, U>, // 1) we can build a T from a U, + std::negation<detail::is_propagate_const<std::decay_t<U>>>, // 2) we are not making a + // converting constructor, + std::negation<std::is_convertible<U, T>> // 3) and the conversion from U to T is + // explicit; + >, + bool> = true> + explicit // then, this constructor is explicit. + propagate_const(U &&other) + : m_t(std::forward<U>(other)) + { + } + + // Constructors from other propagate_const (converting constructors) + template <typename U, + std::enable_if_t< // This constructor is enabled if: + std::conjunction_v<std::is_constructible<T, U>, // 1) we can do the conversion, + std::is_convertible<U, T> // 2) and the conversion is implicit; + >, + bool> = true> + /* implicit */ // then, this constructor is implicit. + constexpr propagate_const(propagate_const<U> &&other) + : m_t(std::move(get_underlying(other))) + { + } + + template <typename U, + std::enable_if_t< // This constructor is enabled if: + std::conjunction_v<std::is_constructible<T, U>, // 1) we can do the conversion, + std::negation<std::is_convertible<U, T>> // 2) and the + // conversion is + // explicit; + >, + bool> = true> + explicit // then, this constructor is explicit. + constexpr propagate_const(propagate_const<U> &&other) + : m_t(std::move(get_underlying(other))) + { + } + + // Converting assignment + template <typename U, + std::enable_if_t< // This assignment operator is enabled if + std::is_convertible_v<U, T>, // the conversion from U to T is implicit + bool> = true> + constexpr propagate_const &operator=(propagate_const<U> &&other) + { + m_t = std::move(get_underlying(other)); + return *this; + } + + template <typename U, + std::enable_if_t< // This assignment operator is enabled if: + std::conjunction_v< + std::is_convertible<U, T>, // 1) the conversion from U to T is implicit, + std::negation<detail::is_propagate_const<std::decay_t<U>>> // 2) and U is not a + // propagate_const + >, + bool> = true> + constexpr propagate_const &operator=(U &&other) + { + m_t = std::forward<U>(other); + return *this; + } + + // Swap + constexpr void swap(propagate_const &other) noexcept(std::is_nothrow_swappable_v<T>) + { + using std::swap; + swap(m_t, other.m_t); + } + + // Const observers + constexpr explicit operator bool() const { return static_cast<bool>(m_t); } + + constexpr const element_type *get() const { return get_impl(m_t); } + + constexpr const element_type &operator*() const { return *get(); } + + constexpr const element_type *operator->() const { return get(); } + + // Non-const observers + constexpr element_type *get() { return get_impl(m_t); } + + constexpr element_type &operator*() { return *get(); } + + constexpr element_type *operator->() { return get(); } + + // Non-member utilities: extract the contained object + template <typename U> + friend constexpr auto &get_underlying(propagate_const<U> &p); + + template <typename U> + friend constexpr const auto &get_underlying(const propagate_const<U> &p); + +private: + // Implementation of get() that works with raw pointers and smart + // pointers. Similar to std::to_address, but to_address is C++20, + // and propagate_const spec does not match it. + template <typename U> + static constexpr element_type *get_impl(U *u) + { + return u; + } + + template <typename U> + static constexpr element_type *get_impl(U &u) + { + return u.get(); + } + + template <typename U> + static constexpr const element_type *get_impl(const U *u) + { + return u; + } + + template <typename U> + static constexpr const element_type *get_impl(const U &u) + { + return u.get(); + } + + T m_t; +}; + +// Swap +template <typename T, + std::enable_if_t<std::is_swappable_v<T>, bool> = true> +constexpr void swap(propagate_const<T> &lhs, propagate_const<T> &rhs) + noexcept(noexcept(lhs.swap(rhs))) +{ + lhs.swap(rhs); +} + +// Implementation of get_underlying +template <typename T> +constexpr auto &get_underlying(propagate_const<T> &p) +{ + return p.m_t; +} + +template <typename T> +constexpr const auto &get_underlying(const propagate_const<T> &p) +{ + return p.m_t; +} + +// Implementation of the conversion operators +template <typename T> +constexpr detail::propagate_const_const_conversion_operator_base<T, true> + ::operator const detail::propagate_const_element_type<T> *() const +{ + return static_cast<const propagate_const<T> *>(this)->get(); +} + +template <typename T> +constexpr detail::propagate_const_non_const_conversion_operator_base<T, true> + ::operator detail::propagate_const_element_type<T> *() +{ + return static_cast<propagate_const<T> *>(this)->get(); +} + +// Comparisons. As per spec, they're free function templates. + +// Comparisons against nullptr. +template <typename T> +constexpr bool operator==(const propagate_const<T> &p, std::nullptr_t) +{ + return get_underlying(p) == nullptr; +} + +template <typename T> +constexpr bool operator!=(const propagate_const<T> &p, std::nullptr_t) +{ + return get_underlying(p) != nullptr; +} + +template <typename T> +constexpr bool operator==(std::nullptr_t, const propagate_const<T> &p) +{ + return nullptr == get_underlying(p); +} + +template <typename T> +constexpr bool operator!=(std::nullptr_t, const propagate_const<T> &p) +{ + return nullptr == get_underlying(p); +} + +// Comparisons between propagate_const +#define DEFINE_PROPAGATE_CONST_COMPARISON_OP(op) \ +template <typename T, typename U> \ + constexpr bool operator op (const propagate_const<T> &lhs, const propagate_const<U> &rhs) \ +{ \ + return get_underlying(lhs) op get_underlying(rhs); \ +} \ + + DEFINE_PROPAGATE_CONST_COMPARISON_OP(==) + DEFINE_PROPAGATE_CONST_COMPARISON_OP(!=) + DEFINE_PROPAGATE_CONST_COMPARISON_OP(<) + DEFINE_PROPAGATE_CONST_COMPARISON_OP(<=) + DEFINE_PROPAGATE_CONST_COMPARISON_OP(>) + DEFINE_PROPAGATE_CONST_COMPARISON_OP(>=) + +#undef DEFINE_PROPAGATE_CONST_COMPARISON_OP + +// Comparisons against other (smart) pointers +#define DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(op) \ + template <typename T, typename U> \ + constexpr bool operator op (const propagate_const<T> &lhs, const U &rhs) \ +{ \ + return get_underlying(lhs) op rhs; \ +} \ + template <typename T, typename U> \ + constexpr bool operator op (const T &lhs, const propagate_const<U> &rhs) \ +{ \ + return lhs op get_underlying(rhs); \ +} \ + + DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(==) + DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(!=) + DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(<) + DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(<=) + DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(>) + DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(>=) + +#undef DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP + +} // namespace KDToolBox + +// std::hash specialization +namespace std +{ +template <typename T> +struct hash<KDToolBox::propagate_const<T>> +{ + constexpr size_t operator()(const KDToolBox::propagate_const<T> &t) const + noexcept(noexcept(hash<T>{}(get_underlying(t)))) + { + return hash<T>{}(get_underlying(t)); + } +}; + +#define DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(COMP) \ +template <typename T> \ + struct COMP<KDToolBox::propagate_const<T>> \ +{ \ + constexpr bool operator()(const KDToolBox::propagate_const<T> &lhs, \ + const KDToolBox::propagate_const<T> &rhs) \ + { \ + return COMP<T>{}(get_underlying(lhs), get_underlying(rhs)); \ + } \ +}; \ + + DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(equal_to) + DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(not_equal_to) + DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(less) + DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(greater) + DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(less_equal) + DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(greater_equal) + +#undef DEFINE_COMP_OBJECT_SPECIALIZATION + +} // namespace std + +#endif // KDTOOLBOX_PROPAGATE_CONST |