From 8a8738a981e1d46146939f93ac0d751b827e7704 Mon Sep 17 00:00:00 2001 From: Andrey Semashev Date: Sat, 7 Jan 2023 01:52:02 +0300 Subject: [PATCH] Propagate noexcept specification in boost::swap. Mark boost::swap noexcept if the type supports non-throwing swap implementation. --- doc/changes.qbk | 1 + doc/swap.qbk | 40 +++++++++++++++++++---------------- include/boost/core/swap.hpp | 29 ++++++++++++++++++------- test/swap/Jamfile.v2 | 1 + test/swap/swap_noexcept.cpp | 42 +++++++++++++++++++++++++++++++++++++ 5 files changed, 88 insertions(+), 25 deletions(-) create mode 100644 test/swap/swap_noexcept.cpp diff --git a/doc/changes.qbk b/doc/changes.qbk index 02a5352..1069b78 100644 --- a/doc/changes.qbk +++ b/doc/changes.qbk @@ -15,6 +15,7 @@ Users are advised to use [@http://www.boost.org/doc/libs/release/libs/type_traits/doc/html/index.html Boost.TypeTraits] or C++ standard library type traits instead. * Marked `boost::ref` member functions and associated methods with `noexcept`. +* Marked `boost::swap` function with `noexcept`, depending on whether the type supports a non-throwing swap operation. [endsect] diff --git a/doc/swap.qbk b/doc/swap.qbk index dc866fd..5770b9d 100644 --- a/doc/swap.qbk +++ b/doc/swap.qbk @@ -22,7 +22,7 @@ [section Header ] -`template void swap(T& left, T& right);` +[^template void swap(T& left, T& right) noexcept(['see below]);] [endsect] @@ -40,13 +40,14 @@ specialized swap function is available, `std::swap` is used. The generic `std::swap` function requires that the elements to be swapped are assignable and copy constructible. It is usually implemented using one copy construction and two -assignments - this is often both unnecessarily restrictive and -unnecessarily slow. In addition, where the generic swap -implementation provides only the basic guarantee, specialized -swap functions are often able to provide the no-throw exception -guarantee (and it is considered best practice to do so where -possible [footnote Scott Meyers, Effective C++ Third Edition, -Item 25: "Consider support for a non-throwing swap"]. +assignments (C++11 replaces copy operations with move) - this +is often both unnecessarily restrictive and unnecessarily slow. +In addition, where the generic swap implementation provides +only the basic guarantee, specialized swap functions are often +able to provide the no-throw exception guarantee (and it is +considered best practice to do so where possible[footnote Scott +Meyers, Effective C++ Third Edition, Item 25: "Consider support +for a non-throwing swap"]. The alternative to using argument dependent lookup in this situation is to provide a template specialization of @@ -59,12 +60,12 @@ in their own namespaces. `std::swap` originally did not do so, but a request to add an overload of `std::swap` for built-in arrays has been accepted by the C++ Standards Committee[footnote - [@http://open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#809 + [@http://open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#809 LWG Defect Report 809: std::swap should be overloaded for array types]]. [endsect] - + [section Exception Safety] `boost::swap` provides the same exception guarantee as the @@ -73,30 +74,33 @@ of type `T[n]`, where `n > 1` and the underlying swap function for `T` provides the strong exception guarantee, `boost::swap` provides only the basic exception guarantee. +In C++11 and later, `boost::swap` propagates the same `noexcept` +specification as the one specified in the underlying swap function. + [endsect] [section Requirements] Either: -* T must be assignable -* T must be copy constructible +* `T` must be copy assignable (/since C++11:/ move assignable) +* `T` must be copy constructible (/since C++11:/ move constructible) Or: -* A function with the signature `swap(T&,T&)` is available via +* A function with the signature `swap(T&, T&)` is available via argument dependent lookup Or: -* A template specialization of `std::swap` exists for T +* A template specialization of `std::swap` exists for `T` Or: -* T is a built-in array of swappable elements +* `T` is a built-in array of swappable elements [endsect] - + [section Portability] Several older compilers do not support argument dependent @@ -104,11 +108,11 @@ lookup. On these compilers `boost::swap` will call `std::swap`, ignoring any specialized swap functions that could be found as a result of argument dependent lookup. -[endsect] +[endsect] [section Credits] -* *Niels Dekker* - for implementing and documenting support for +* *Niels Dekker* - for implementing and documenting support for built-in arrays * *Joseph Gauterin* - for the initial idea, implementation, tests, and documentation diff --git a/include/boost/core/swap.hpp b/include/boost/core/swap.hpp index 49e1b2d..9952779 100644 --- a/include/boost/core/swap.hpp +++ b/include/boost/core/swap.hpp @@ -13,10 +13,10 @@ // - swap_impl is put outside the boost namespace, to avoid infinite // recursion (causing stack overflow) when swapping objects of a primitive // type. -// - swap_impl has a using-directive, rather than a using-declaration, -// because some compilers (including MSVC 7.1, Borland 5.9.3, and -// Intel 8.1) don't do argument-dependent lookup when it has a -// using-declaration instead. +// - std::swap is imported with a using-directive, rather than +// a using-declaration, because some compilers (including MSVC 7.1, +// Borland 5.9.3, and Intel 8.1) don't do argument-dependent lookup +// when it has a using-declaration instead. // - boost::swap has two template arguments, instead of one, to // avoid ambiguity when swapping objects of a Boost type that does // not have its own boost::swap overload. @@ -30,6 +30,13 @@ #endif #include // for std::size_t +#if defined(BOOST_GCC) && (BOOST_GCC < 40700) +// gcc 4.6 ICEs on noexcept specifications below +#define BOOST_CORE_SWAP_NOEXCEPT_IF(x) +#else +#define BOOST_CORE_SWAP_NOEXCEPT_IF(x) BOOST_NOEXCEPT_IF(x) +#endif + namespace boost_swap_impl { // we can't use type_traits here @@ -37,17 +44,22 @@ namespace boost_swap_impl template struct is_const { enum _vt { value = 0 }; }; template struct is_const { enum _vt { value = 1 }; }; + // Use std::swap if argument dependent lookup fails. + // We need to have this at namespace scope to be able to use unqualified swap() call + // in noexcept specification. + using namespace std; + template BOOST_GPU_ENABLED - void swap_impl(T& left, T& right) + void swap_impl(T& left, T& right) BOOST_CORE_SWAP_NOEXCEPT_IF(BOOST_NOEXCEPT_EXPR(swap(left, right))) { - using namespace std;//use std::swap if argument dependent lookup fails - swap(left,right); + swap(left, right); } template BOOST_GPU_ENABLED void swap_impl(T (& left)[N], T (& right)[N]) + BOOST_CORE_SWAP_NOEXCEPT_IF(BOOST_NOEXCEPT_EXPR(::boost_swap_impl::swap_impl(left[0], right[0]))) { for (std::size_t i = 0; i < N; ++i) { @@ -62,9 +74,12 @@ namespace boost BOOST_GPU_ENABLED typename enable_if_c< !boost_swap_impl::is_const::value && !boost_swap_impl::is_const::value >::type swap(T1& left, T2& right) + BOOST_CORE_SWAP_NOEXCEPT_IF(BOOST_NOEXCEPT_EXPR(::boost_swap_impl::swap_impl(left, right))) { ::boost_swap_impl::swap_impl(left, right); } } +#undef BOOST_CORE_SWAP_NOEXCEPT_IF + #endif diff --git a/test/swap/Jamfile.v2 b/test/swap/Jamfile.v2 index 2b43737..d7f705e 100644 --- a/test/swap/Jamfile.v2 +++ b/test/swap/Jamfile.v2 @@ -13,6 +13,7 @@ compile swap_lib_header_1.cpp ; compile swap_lib_header_2.cpp ; compile swap_mixed_headers_1.cpp ; compile swap_mixed_headers_2.cpp ; +compile swap_noexcept.cpp ; compile-fail swap_const_wrapper_fail.cpp ; diff --git a/test/swap/swap_noexcept.cpp b/test/swap/swap_noexcept.cpp new file mode 100644 index 0000000..cafbde1 --- /dev/null +++ b/test/swap/swap_noexcept.cpp @@ -0,0 +1,42 @@ +// Copyright (c) 2023 Andrey Semashev +// +// 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) + +// Tests that boost::swap propagates noexcept specification correctly + +#include +#include + +#if !defined(BOOST_NO_CXX11_NOEXCEPT) && !defined(BOOST_NO_CXX11_STATIC_ASSERT) && \ + !(defined(BOOST_GCC) && (BOOST_GCC < 40700)) + +namespace test_ns { + +struct class_with_noexcept_swap +{ + static class_with_noexcept_swap& instance() noexcept; + + friend void swap(class_with_noexcept_swap&, class_with_noexcept_swap&) noexcept + { + } +}; + +struct class_with_except_swap +{ + static class_with_except_swap& instance() noexcept; + + friend void swap(class_with_except_swap&, class_with_except_swap&) + { + } +}; + +} // namespace test_ns + +static_assert(noexcept(boost::swap(test_ns::class_with_noexcept_swap::instance(), test_ns::class_with_noexcept_swap::instance())), + "boost::swap for class_with_noexcept_swap should have noexcept specification"); +static_assert(!noexcept(boost::swap(test_ns::class_with_except_swap::instance(), test_ns::class_with_except_swap::instance())), + "boost::swap for class_with_except_swap should not have noexcept specification"); + +#endif // !defined(BOOST_NO_CXX11_NOEXCEPT) && !defined(BOOST_NO_CXX11_STATIC_ASSERT) ...