/**************************************************************************** ** MIT License ** ** Copyright (C) 2022-2023 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com ** Author: Giuseppe D'Angelo ** ** 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 #include #include // Our SFINAE is too noisy and clang-format gets confused. // clang-format off namespace KDToolBox { template class propagate_const; namespace detail { // Type traits to detect propagate_const specializations template struct is_propagate_const : std::false_type { }; template struct is_propagate_const> : 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 using propagate_const_element_type = std::remove_reference_t())>; // Const conversion. // NON-STANDARD: checks that `const T` is convertible, not `T`. // See https://wg21.link/lwg3812 template , std::is_convertible *> > > struct propagate_const_const_conversion_operator_base { }; template struct propagate_const_const_conversion_operator_base { constexpr operator const propagate_const_element_type *() const; }; // Non-const conversion template , std::is_convertible *> > > struct propagate_const_non_const_conversion_operator_base { }; template struct propagate_const_non_const_conversion_operator_base { constexpr operator propagate_const_element_type *(); }; template struct propagate_const_base : propagate_const_const_conversion_operator_base, propagate_const_non_const_conversion_operator_base {}; } // 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 class propagate_const : public detail::propagate_const_base { public: using element_type = detail::propagate_const_element_type; // 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, // 1) we can build a T from a U, std::negation>>, // 2) we are not making a // converting constructor, std::is_convertible // 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(other)) { } template < typename U, std::enable_if_t< // This constructor is enabled if: std::conjunction_v< std::is_constructible, // 1) we can build a T from a U, std::negation>>, // 2) we are not making a // converting constructor, std::negation> // 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(other)) { } // Constructors from other propagate_const (converting constructors) template , // 1) we can do the conversion, std::is_convertible // 2) and the conversion is implicit; >, bool> = true> /* implicit */ // then, this constructor is implicit. constexpr propagate_const(propagate_const &&other) : m_t(std::move(get_underlying(other))) { } template , // 1) we can do the conversion, std::negation> // 2) and the // conversion is // explicit; >, bool> = true> explicit // then, this constructor is explicit. constexpr propagate_const(propagate_const &&other) : m_t(std::move(get_underlying(other))) { } // Converting assignment template , // the conversion from U to T is implicit bool> = true> constexpr propagate_const &operator=(propagate_const &&other) { m_t = std::move(get_underlying(other)); return *this; } template , // 1) the conversion from U to T is implicit, std::negation>> // 2) and U is not a // propagate_const >, bool> = true> constexpr propagate_const &operator=(U &&other) { m_t = std::forward(other); return *this; } // Swap constexpr void swap(propagate_const &other) noexcept(std::is_nothrow_swappable_v) { using std::swap; swap(m_t, other.m_t); } // Const observers constexpr explicit operator bool() const { return static_cast(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 friend constexpr auto &get_underlying(propagate_const &p); template friend constexpr const auto &get_underlying(const propagate_const &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 static constexpr element_type *get_impl(U *u) { return u; } template static constexpr element_type *get_impl(U &u) { return u.get(); } template static constexpr const element_type *get_impl(const U *u) { return u; } template static constexpr const element_type *get_impl(const U &u) { return u.get(); } T m_t; }; // Swap template , bool> = true> constexpr void swap(propagate_const &lhs, propagate_const &rhs) noexcept(noexcept(lhs.swap(rhs))) { lhs.swap(rhs); } // Implementation of get_underlying template constexpr auto &get_underlying(propagate_const &p) { return p.m_t; } template constexpr const auto &get_underlying(const propagate_const &p) { return p.m_t; } // Implementation of the conversion operators template constexpr detail::propagate_const_const_conversion_operator_base ::operator const detail::propagate_const_element_type *() const { return static_cast *>(this)->get(); } template constexpr detail::propagate_const_non_const_conversion_operator_base ::operator detail::propagate_const_element_type *() { return static_cast *>(this)->get(); } // Comparisons. As per spec, they're free function templates. // Comparisons against nullptr. template constexpr bool operator==(const propagate_const &p, std::nullptr_t) { return get_underlying(p) == nullptr; } template constexpr bool operator!=(const propagate_const &p, std::nullptr_t) { return get_underlying(p) != nullptr; } template constexpr bool operator==(std::nullptr_t, const propagate_const &p) { return nullptr == get_underlying(p); } template constexpr bool operator!=(std::nullptr_t, const propagate_const &p) { return nullptr == get_underlying(p); } // Comparisons between propagate_const #define DEFINE_PROPAGATE_CONST_COMPARISON_OP(op) \ template \ constexpr bool operator op (const propagate_const &lhs, const propagate_const &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 \ constexpr bool operator op (const propagate_const &lhs, const U &rhs) \ { \ return get_underlying(lhs) op rhs; \ } \ template \ constexpr bool operator op (const T &lhs, const propagate_const &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 struct hash> { constexpr size_t operator()(const KDToolBox::propagate_const &t) const noexcept(noexcept(hash{}(get_underlying(t)))) { return hash{}(get_underlying(t)); } }; #define DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(COMP) \ template \ struct COMP> \ { \ constexpr bool operator()(const KDToolBox::propagate_const &lhs, \ const KDToolBox::propagate_const &rhs) \ { \ return COMP{}(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