From d08d035bdf4d5d1b9d5c6b798e0b7da8fa9bb325 Mon Sep 17 00:00:00 2001 From: Mathias Stearn Date: Thu, 8 May 2025 14:06:04 +0200 Subject: [PATCH] Enable C++20 constexpr for intrusive_ptr fixes #117 --- .../smart_ptr/detail/sp_cxx20_constexpr.hpp | 37 +++++ include/boost/smart_ptr/intrusive_ptr.hpp | 79 ++++++----- test/Jamfile | 1 + test/ip_constexpr_test.cpp | 133 ++++++++++++++++++ 4 files changed, 211 insertions(+), 39 deletions(-) create mode 100644 include/boost/smart_ptr/detail/sp_cxx20_constexpr.hpp create mode 100644 test/ip_constexpr_test.cpp diff --git a/include/boost/smart_ptr/detail/sp_cxx20_constexpr.hpp b/include/boost/smart_ptr/detail/sp_cxx20_constexpr.hpp new file mode 100644 index 0000000..dc7c9ca --- /dev/null +++ b/include/boost/smart_ptr/detail/sp_cxx20_constexpr.hpp @@ -0,0 +1,37 @@ +#ifndef BOOST_SMART_PTR_DETAIL_SP_CXX20_CONSTEXPR_HPP_INCLUDED +#define BOOST_SMART_PTR_DETAIL_SP_CXX20_CONSTEXPR_HPP_INCLUDED + +// MS compatible compilers support #pragma once + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +#pragma once +#endif + +// detail/sp_noexcept.hpp +// +// Copyright 2025 Mathias Stearn +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt + + +// This macro is used to mark functions as constexpr if the compiler supports +// constexpr destructors. Since you can't have a constexpr smart pointer object, +// everything except null constructors are guided behind this macro. Because +// this also guards a use of dynamic_cast, we need to check for its availability +// as well. It isn't worth splitting out since all known compilers that support +// constexpr dynamic_cast also support constexpr destructors. +// +// WARNING: This does not check for changing active member of a union in +// constant expressions which is allowed in C++20. If that is needed, we +// need to raise the checked version to 202002L. +#if defined(__cpp_constexpr_dynamic_alloc) && __cpp_constexpr_dynamic_alloc >= 201907L \ + && defined(__cpp_constexpr) && __cpp_constexpr >= 201907L +#define BOOST_SP_CXX20_CONSTEXPR constexpr +#else +#define BOOST_SP_CXX20_CONSTEXPR +#define BOOST_SP_NO_CXX20_CONSTEXPR +#endif + +#endif // #ifndef BOOST_SMART_PTR_DETAIL_SP_CXX20_CONSTEXPR_HPP_INCLUDED diff --git a/include/boost/smart_ptr/intrusive_ptr.hpp b/include/boost/smart_ptr/intrusive_ptr.hpp index 11b4765..ce99b74 100644 --- a/include/boost/smart_ptr/intrusive_ptr.hpp +++ b/include/boost/smart_ptr/intrusive_ptr.hpp @@ -13,6 +13,7 @@ // See http://www.boost.org/libs/smart_ptr/ for documentation. // +#include #include #include #include @@ -53,29 +54,29 @@ public: { } - intrusive_ptr( T * p, bool add_ref = true ): px( p ) + BOOST_SP_CXX20_CONSTEXPR intrusive_ptr( T * p, bool add_ref = true ): px( p ) { if( px != 0 && add_ref ) intrusive_ptr_add_ref( px ); } template - intrusive_ptr( intrusive_ptr const & rhs, typename boost::detail::sp_enable_if_convertible::type = boost::detail::sp_empty() ) + BOOST_SP_CXX20_CONSTEXPR intrusive_ptr( intrusive_ptr const & rhs, typename boost::detail::sp_enable_if_convertible::type = boost::detail::sp_empty() ) : px( rhs.get() ) { if( px != 0 ) intrusive_ptr_add_ref( px ); } - intrusive_ptr(intrusive_ptr const & rhs): px( rhs.px ) + BOOST_SP_CXX20_CONSTEXPR intrusive_ptr(intrusive_ptr const & rhs): px( rhs.px ) { if( px != 0 ) intrusive_ptr_add_ref( px ); } - ~intrusive_ptr() + BOOST_SP_CXX20_CONSTEXPR ~intrusive_ptr() { if( px != 0 ) intrusive_ptr_release( px ); } - template intrusive_ptr & operator=(intrusive_ptr const & rhs) + template BOOST_SP_CXX20_CONSTEXPR intrusive_ptr & operator=(intrusive_ptr const & rhs) { this_type(rhs).swap(*this); return *this; @@ -83,12 +84,12 @@ public: // Move support - intrusive_ptr(intrusive_ptr && rhs) noexcept : px( rhs.px ) + BOOST_SP_CXX20_CONSTEXPR intrusive_ptr(intrusive_ptr && rhs) noexcept : px( rhs.px ) { rhs.px = 0; } - intrusive_ptr & operator=(intrusive_ptr && rhs) noexcept + BOOST_SP_CXX20_CONSTEXPR intrusive_ptr & operator=(intrusive_ptr && rhs) noexcept { this_type( static_cast< intrusive_ptr && >( rhs ) ).swap(*this); return *this; @@ -97,76 +98,76 @@ public: template friend class intrusive_ptr; template - intrusive_ptr(intrusive_ptr && rhs, typename boost::detail::sp_enable_if_convertible::type = boost::detail::sp_empty()) + BOOST_SP_CXX20_CONSTEXPR intrusive_ptr(intrusive_ptr && rhs, typename boost::detail::sp_enable_if_convertible::type = boost::detail::sp_empty()) : px( rhs.px ) { rhs.px = 0; } template - intrusive_ptr & operator=(intrusive_ptr && rhs) noexcept + BOOST_SP_CXX20_CONSTEXPR intrusive_ptr & operator=(intrusive_ptr && rhs) noexcept { this_type( static_cast< intrusive_ptr && >( rhs ) ).swap(*this); return *this; } - intrusive_ptr & operator=(intrusive_ptr const & rhs) + BOOST_SP_CXX20_CONSTEXPR intrusive_ptr & operator=(intrusive_ptr const & rhs) { this_type(rhs).swap(*this); return *this; } - intrusive_ptr & operator=(T * rhs) + BOOST_SP_CXX20_CONSTEXPR intrusive_ptr & operator=(T * rhs) { this_type(rhs).swap(*this); return *this; } - void reset() + BOOST_SP_CXX20_CONSTEXPR void reset() { this_type().swap( *this ); } - void reset( T * rhs ) + BOOST_SP_CXX20_CONSTEXPR void reset( T * rhs ) { this_type( rhs ).swap( *this ); } - void reset( T * rhs, bool add_ref ) + BOOST_SP_CXX20_CONSTEXPR void reset( T * rhs, bool add_ref ) { this_type( rhs, add_ref ).swap( *this ); } - T * get() const noexcept + BOOST_SP_CXX20_CONSTEXPR T * get() const noexcept { return px; } - T * detach() noexcept + BOOST_SP_CXX20_CONSTEXPR T * detach() noexcept { T * ret = px; px = 0; return ret; } - T & operator*() const BOOST_SP_NOEXCEPT_WITH_ASSERT + BOOST_SP_CXX20_CONSTEXPR T & operator*() const BOOST_SP_NOEXCEPT_WITH_ASSERT { BOOST_ASSERT( px != 0 ); return *px; } - T * operator->() const BOOST_SP_NOEXCEPT_WITH_ASSERT + BOOST_SP_CXX20_CONSTEXPR T * operator->() const BOOST_SP_NOEXCEPT_WITH_ASSERT { BOOST_ASSERT( px != 0 ); return px; } - explicit operator bool () const noexcept + BOOST_SP_CXX20_CONSTEXPR explicit operator bool () const noexcept { return px != 0; } - void swap(intrusive_ptr & rhs) noexcept + BOOST_SP_CXX20_CONSTEXPR void swap(intrusive_ptr & rhs) noexcept { T * tmp = px; px = rhs.px; @@ -178,101 +179,101 @@ private: T * px; }; -template inline bool operator==(intrusive_ptr const & a, intrusive_ptr const & b) noexcept +template BOOST_SP_CXX20_CONSTEXPR inline bool operator==(intrusive_ptr const & a, intrusive_ptr const & b) noexcept { return a.get() == b.get(); } -template inline bool operator!=(intrusive_ptr const & a, intrusive_ptr const & b) noexcept +template BOOST_SP_CXX20_CONSTEXPR inline bool operator!=(intrusive_ptr const & a, intrusive_ptr const & b) noexcept { return a.get() != b.get(); } -template inline bool operator==(intrusive_ptr const & a, U * b) noexcept +template BOOST_SP_CXX20_CONSTEXPR inline bool operator==(intrusive_ptr const & a, U * b) noexcept { return a.get() == b; } -template inline bool operator!=(intrusive_ptr const & a, U * b) noexcept +template BOOST_SP_CXX20_CONSTEXPR inline bool operator!=(intrusive_ptr const & a, U * b) noexcept { return a.get() != b; } -template inline bool operator==(T * a, intrusive_ptr const & b) noexcept +template BOOST_SP_CXX20_CONSTEXPR inline bool operator==(T * a, intrusive_ptr const & b) noexcept { return a == b.get(); } -template inline bool operator!=(T * a, intrusive_ptr const & b) noexcept +template BOOST_SP_CXX20_CONSTEXPR inline bool operator!=(T * a, intrusive_ptr const & b) noexcept { return a != b.get(); } -template inline bool operator==( intrusive_ptr const & p, std::nullptr_t ) noexcept +template BOOST_SP_CXX20_CONSTEXPR inline bool operator==( intrusive_ptr const & p, std::nullptr_t ) noexcept { return p.get() == 0; } -template inline bool operator==( std::nullptr_t, intrusive_ptr const & p ) noexcept +template BOOST_SP_CXX20_CONSTEXPR inline bool operator==( std::nullptr_t, intrusive_ptr const & p ) noexcept { return p.get() == 0; } -template inline bool operator!=( intrusive_ptr const & p, std::nullptr_t ) noexcept +template BOOST_SP_CXX20_CONSTEXPR inline bool operator!=( intrusive_ptr const & p, std::nullptr_t ) noexcept { return p.get() != 0; } -template inline bool operator!=( std::nullptr_t, intrusive_ptr const & p ) noexcept +template BOOST_SP_CXX20_CONSTEXPR inline bool operator!=( std::nullptr_t, intrusive_ptr const & p ) noexcept { return p.get() != 0; } -template inline bool operator<(intrusive_ptr const & a, intrusive_ptr const & b) noexcept +template BOOST_SP_CXX20_CONSTEXPR inline bool operator<(intrusive_ptr const & a, intrusive_ptr const & b) noexcept { return std::less()(a.get(), b.get()); } -template void swap(intrusive_ptr & lhs, intrusive_ptr & rhs) noexcept +template BOOST_SP_CXX20_CONSTEXPR inline void swap(intrusive_ptr & lhs, intrusive_ptr & rhs) noexcept { lhs.swap(rhs); } // mem_fn support -template T * get_pointer(intrusive_ptr const & p) noexcept +template BOOST_SP_CXX20_CONSTEXPR inline T * get_pointer(intrusive_ptr const & p) noexcept { return p.get(); } // pointer casts -template intrusive_ptr static_pointer_cast(intrusive_ptr const & p) +template BOOST_SP_CXX20_CONSTEXPR inline intrusive_ptr static_pointer_cast(intrusive_ptr const & p) { return static_cast(p.get()); } -template intrusive_ptr const_pointer_cast(intrusive_ptr const & p) +template BOOST_SP_CXX20_CONSTEXPR inline intrusive_ptr const_pointer_cast(intrusive_ptr const & p) { return const_cast(p.get()); } -template intrusive_ptr dynamic_pointer_cast(intrusive_ptr const & p) +template BOOST_SP_CXX20_CONSTEXPR inline intrusive_ptr dynamic_pointer_cast(intrusive_ptr const & p) { return dynamic_cast(p.get()); } -template intrusive_ptr static_pointer_cast( intrusive_ptr && p ) noexcept +template BOOST_SP_CXX20_CONSTEXPR inline intrusive_ptr static_pointer_cast( intrusive_ptr && p ) noexcept { return intrusive_ptr( static_cast( p.detach() ), false ); } -template intrusive_ptr const_pointer_cast( intrusive_ptr && p ) noexcept +template BOOST_SP_CXX20_CONSTEXPR inline intrusive_ptr const_pointer_cast( intrusive_ptr && p ) noexcept { return intrusive_ptr( const_cast( p.detach() ), false ); } -template intrusive_ptr dynamic_pointer_cast( intrusive_ptr && p ) noexcept +template BOOST_SP_CXX20_CONSTEXPR inline intrusive_ptr dynamic_pointer_cast( intrusive_ptr && p ) noexcept { T * p2 = dynamic_cast( p.get() ); diff --git a/test/Jamfile b/test/Jamfile index ea702ac..2132372 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -245,6 +245,7 @@ run atomic_sp_test.cpp ; run sp_constexpr_test.cpp ; run sp_constexpr_test2.cpp ; +compile ip_constexpr_test.cpp ; run atomic_sp_constexpr_test.cpp ; diff --git a/test/ip_constexpr_test.cpp b/test/ip_constexpr_test.cpp new file mode 100644 index 0000000..88cd4c7 --- /dev/null +++ b/test/ip_constexpr_test.cpp @@ -0,0 +1,133 @@ +// +// ip_constexpr_test.cpp +// +// Copyright 2025 Mathias Stearn +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// + +#include + +#ifndef BOOST_SP_NO_CXX20_CONSTEXPR + +struct dummy { + // no-ops, so safe on pointers to static constexpr variables + friend constexpr void intrusive_ptr_add_ref(const dummy *) {} + friend constexpr void intrusive_ptr_release(const dummy *) {} +}; +static constexpr dummy d; + +struct subdummy : dummy {}; + +// Test that basic operations work at compile time. +static_assert(bool(boost::intrusive_ptr(&d))); +static_assert(!bool(boost::intrusive_ptr(nullptr))); +static_assert(!bool(boost::intrusive_ptr())); +static_assert(!bool(boost::intrusive_ptr(boost::intrusive_ptr()))); +static_assert(&*boost::intrusive_ptr(&d) == &d); +static_assert(boost::intrusive_ptr(&d).operator->() == &d); +static_assert(boost::intrusive_ptr() == nullptr); +static_assert(boost::intrusive_ptr() == boost::intrusive_ptr(nullptr)); +static_assert(boost::intrusive_ptr() != boost::intrusive_ptr(&d)); +static_assert(boost::intrusive_ptr(&d) != nullptr); +static_assert(boost::intrusive_ptr(&d) == boost::intrusive_ptr(&d)); +static_assert(boost::intrusive_ptr(&d) == boost::intrusive_ptr(&d).get()); +static_assert(boost::intrusive_ptr(&d) == boost::intrusive_ptr(&d).detach()); +static_assert(!(boost::intrusive_ptr(&d) < boost::intrusive_ptr(&d))); +static_assert(boost::get_pointer(boost::intrusive_ptr(&d)) == &d); +static_assert(boost::static_pointer_cast( boost::intrusive_ptr(&d)) == &d); +static_assert(boost::const_pointer_cast( boost::intrusive_ptr(&d)) == &d); +static_assert(boost::dynamic_pointer_cast( boost::intrusive_ptr(&d)) == &d); + +constexpr auto lvalue = boost::intrusive_ptr(&d); +constexpr auto lvalue_convertible = boost::intrusive_ptr(); +static_assert(boost::intrusive_ptr(lvalue) == &d); +static_assert(!boost::intrusive_ptr(lvalue_convertible)); +static_assert(boost::static_pointer_cast(lvalue) == &d); +static_assert(boost::const_pointer_cast(lvalue) == &d); +static_assert(boost::dynamic_pointer_cast(lvalue) == &d); + +// Works in places that static_assert doesn't, like expressions with +// non-constexpr variables in constexpr functions. +template constexpr void semi_static_assert(T b) { + if (!b) + throw "assertion failed"; // Not constexpr so fails compile. +} + +constexpr bool test_swap() { + auto p1 = boost::intrusive_ptr(&d); + auto p2 = boost::intrusive_ptr(); + swap(p1, p2); + semi_static_assert(!p1 && p2); + p1.swap(p2); + semi_static_assert(p1 && !p2); + return true; +} +static_assert(test_swap()); + +constexpr bool test_reset_assign() { + // Test assignments resulting in nullptr + { + auto p1 = boost::intrusive_ptr(&d); + p1.reset(); + semi_static_assert(!p1); + } + { + auto p1 = boost::intrusive_ptr(&d); + p1.reset(nullptr); + semi_static_assert(!p1); + } + { + auto p1 = boost::intrusive_ptr(&d); + p1 = nullptr; + semi_static_assert(!p1); + } + { + auto p1 = boost::intrusive_ptr(&d); + p1 = boost::intrusive_ptr(); + semi_static_assert(!p1); + } + { + auto p1 = boost::intrusive_ptr(&d); + p1 = boost::intrusive_ptr(); + semi_static_assert(!p1); + } + { + auto p1 = boost::intrusive_ptr(&d); + p1 = lvalue_convertible; + semi_static_assert(!p1); + } + + // Test assignments resulting in &d + { + auto p1 = boost::intrusive_ptr(); + p1.reset(&d); + semi_static_assert(p1 == &d); + } + { + auto p1 = boost::intrusive_ptr(); + p1.reset(&d, true); + semi_static_assert(p1 == &d); + } + { + auto p1 = boost::intrusive_ptr(); + p1 = boost::intrusive_ptr(&d); + semi_static_assert(p1 == &d); + } + { + auto p1 = boost::intrusive_ptr(); + p1 = lvalue; + semi_static_assert(p1 == &d); + } + { + auto p1 = boost::intrusive_ptr(); + p1 = &d; + semi_static_assert(p1 == &d); + } + return true; +} +static_assert(test_reset_assign()); + +#endif \ No newline at end of file