On Mon, 12 May 2025, Tomasz Kamiński wrote: > This patch implements C++26 copyable_function as specified in P2548R6. > It also implements LWG 4255 that adjust move_only_function so constructing > from empty copyable_function, produces empty functor. This falls from > existing checks, after specializing __is_polymorphic_function_v for > copyable_function specializations. > > For compatible invoker signatures, the move_only_function may be constructed > from copyable_funciton without double indirection. To achieve that we derive > _Cpy_base from _Mo_base, and specialize __is_polymorphic_function_v for > copyable_function. Similary copyable_functions with compatible signatures > can be converted without double indirection. > > As we starting to use _Op::_Copy operation from the _M_manage function, > invocations of that functions may now throw exceptions, so noexcept needs > to be removed from the signature of stored _M_manage pointers. This also > affects operations in _Mo_base, however we already wrap _M_manage invocations > in noexcept member functions (_M_move, _M_destroy, swap). > > PR libstdc++/119125 > > libstdc++-v3/ChangeLog: > > * doc/doxygen/stdheader.cc: Addded cpyfunc_impl.h header. > * include/Makefile.am: Add bits cpyfunc_impl.h. > * include/Makefile.in: Add bits cpyfunc_impl.h. > * include/bits/cpyfunc_impl.h: New file. > * include/bits/mofunc_impl.h: Mention LWG 4255. > * include/bits/move_only_function.h: Update header description > and change guard to __cplusplus > 202002L. > (_Manager::_Func): Remove noexcept. > (std::__is_polymorphic_function_v<move_only_function<_Tp>>) > > (__variant::_Never_valueless_alt<std::move_only_function<_Signature...>>) > (move_only_function) [__glibcxx_move_only_function]: Adjust guard. > (std::__is_polymorphic_function_v<copyable_function<_Tp>>) > (__variant::_Never_valueless_alt<std::copyable_function<_Signature...>>) > (__polyfunc::_Cpy_base, std::copyable_function) > [__glibcxx_copyable_function]: > Define. > * include/bits/version.def: Define copyable_function. > * include/bits/version.h: Regenerate. > * include/std/functional: Define __cpp_lib_copyable_function. > * testsuite/20_util/copyable_function/call.cc: New test based on > move_only_function tests. > * testsuite/20_util/copyable_function/cons.cc: New test based on > move_only_function tests. > * testsuite/20_util/copyable_function/conv.cc: New test based on > move_only_function tests. > * testsuite/20_util/copyable_function/copy.cc: New test. > * testsuite/20_util/copyable_function/move.cc: New test based on > move_only_function tests. > --- > libstdc++-v3/doc/doxygen/stdheader.cc | 1 + > libstdc++-v3/include/Makefile.am | 1 + > libstdc++-v3/include/Makefile.in | 1 + > libstdc++-v3/include/bits/cpyfunc_impl.h | 268 ++++++++++++++++++ > libstdc++-v3/include/bits/mofunc_impl.h | 4 + > .../include/bits/move_only_function.h | 91 +++++- > libstdc++-v3/include/bits/version.def | 10 + > libstdc++-v3/include/bits/version.h | 10 + > libstdc++-v3/include/std/functional | 1 + > .../20_util/copyable_function/call.cc | 224 +++++++++++++++ > .../20_util/copyable_function/cons.cc | 126 ++++++++ > .../20_util/copyable_function/conv.cc | 253 +++++++++++++++++ > .../20_util/copyable_function/copy.cc | 154 ++++++++++ > .../20_util/copyable_function/move.cc | 120 ++++++++ > 14 files changed, 1260 insertions(+), 4 deletions(-) > create mode 100644 libstdc++-v3/include/bits/cpyfunc_impl.h > create mode 100644 libstdc++-v3/testsuite/20_util/copyable_function/call.cc > create mode 100644 libstdc++-v3/testsuite/20_util/copyable_function/cons.cc > create mode 100644 libstdc++-v3/testsuite/20_util/copyable_function/conv.cc > create mode 100644 libstdc++-v3/testsuite/20_util/copyable_function/copy.cc > create mode 100644 libstdc++-v3/testsuite/20_util/copyable_function/move.cc > > diff --git a/libstdc++-v3/doc/doxygen/stdheader.cc > b/libstdc++-v3/doc/doxygen/stdheader.cc > index 3ee825feb66..8a201334410 100644 > --- a/libstdc++-v3/doc/doxygen/stdheader.cc > +++ b/libstdc++-v3/doc/doxygen/stdheader.cc > @@ -54,6 +54,7 @@ void init_map() > headers["function.h"] = "functional"; > headers["functional_hash.h"] = "functional"; > headers["mofunc_impl.h"] = "functional"; > + headers["cpyfunc_impl.h"] = "functional"; > headers["move_only_function.h"] = "functional"; > headers["invoke.h"] = "functional"; > headers["ranges_cmp.h"] = "functional"; > diff --git a/libstdc++-v3/include/Makefile.am > b/libstdc++-v3/include/Makefile.am > index 1140fa0dffd..5cc13381b02 100644 > --- a/libstdc++-v3/include/Makefile.am > +++ b/libstdc++-v3/include/Makefile.am > @@ -194,6 +194,7 @@ bits_headers = \ > ${bits_srcdir}/chrono_io.h \ > ${bits_srcdir}/codecvt.h \ > ${bits_srcdir}/cow_string.h \ > + ${bits_srcdir}/cpyfunc_impl.h \ > ${bits_srcdir}/deque.tcc \ > ${bits_srcdir}/erase_if.h \ > ${bits_srcdir}/formatfwd.h \ > diff --git a/libstdc++-v3/include/Makefile.in > b/libstdc++-v3/include/Makefile.in > index c96e981acd6..6e5e97aa236 100644 > --- a/libstdc++-v3/include/Makefile.in > +++ b/libstdc++-v3/include/Makefile.in > @@ -547,6 +547,7 @@ bits_freestanding = \ > @GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/chrono_io.h \ > @GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/codecvt.h \ > @GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/cow_string.h \ > +@GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/cpyfunc_impl.h \ > @GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/deque.tcc \ > @GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/erase_if.h \ > @GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/formatfwd.h \ > diff --git a/libstdc++-v3/include/bits/cpyfunc_impl.h > b/libstdc++-v3/include/bits/cpyfunc_impl.h > new file mode 100644 > index 00000000000..c2840032494 > --- /dev/null > +++ b/libstdc++-v3/include/bits/cpyfunc_impl.h > @@ -0,0 +1,268 @@ > +// Implementation of std::copyable_function -*- C++ -*- > + > +// Copyright The GNU Toolchain Authors. > +// > +// This file is part of the GNU ISO C++ Library. This library 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, or (at your option) > +// any later version. > + > +// This library 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. > + > +// Under Section 7 of GPL version 3, you are granted additional > +// permissions described in the GCC Runtime Library Exception, version > +// 3.1, as published by the Free Software Foundation. > + > +// You should have received a copy of the GNU General Public License and > +// a copy of the GCC Runtime Library Exception along with this program; > +// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see > +// <http://www.gnu.org/licenses/>. > + > +/** @file include/bits/cpyfunc_impl.h > + * This is an internal header file, included by other library headers. > + * Do not attempt to use it directly. @headername{functional} > + */ > + > +#ifndef _GLIBCXX_MOF_CV > +# define _GLIBCXX_MOF_CV > +#endif > + > +#ifdef _GLIBCXX_MOF_REF > +# define _GLIBCXX_MOF_INV_QUALS _GLIBCXX_MOF_CV _GLIBCXX_MOF_REF > +#else > +# define _GLIBCXX_MOF_REF > +# define _GLIBCXX_MOF_INV_QUALS _GLIBCXX_MOF_CV & > +#endif > + > +#define _GLIBCXX_MOF_CV_REF _GLIBCXX_MOF_CV _GLIBCXX_MOF_REF > + > +namespace std _GLIBCXX_VISIBILITY(default) > +{ > +_GLIBCXX_BEGIN_NAMESPACE_VERSION > + > + /** > + * @brief Polymorphic function wrapper. > + * @ingroup functors > + * @since C++23 > + * @headerfile functional > + * > + * The `std::copyable_function` class template is a call wrapper similar > + * to `std::function`, but does not require the stored target function > + * to be copyable. > + * > + * It also supports const-qualification, ref-qualification, and > + * no-throw guarantees. The qualifications and exception-specification > + * of the `copyable_function::operator()` member function are respected > + * when invoking the target function. > + */ > + template<typename _Res, typename... _ArgTypes, bool _Noex> > + class copyable_function<_Res(_ArgTypes...) _GLIBCXX_MOF_CV > + _GLIBCXX_MOF_REF noexcept(_Noex)> > + : __polyfunc::_Cpy_base
Shall we export std::copyable_function from the std module in this same commit? > + { > + using _Base = __polyfunc::_Cpy_base; > + using _Invoker = __polyfunc::_Invoker<_Noex, _Res, _ArgTypes...>; > + using _Signature = _Invoker::_Signature; > + > + template<typename _Tp> > + using __callable > + = __conditional_t<_Noex, > + is_nothrow_invocable_r<_Res, _Tp, _ArgTypes...>, > + is_invocable_r<_Res, _Tp, _ArgTypes...>>; > + > + // [func.wrap.mov.con]/1 is-callable-from<VT> > + template<typename _Vt> > + static constexpr bool __is_callable_from > + = __and_v<__callable<_Vt _GLIBCXX_MOF_CV_REF>, > + __callable<_Vt _GLIBCXX_MOF_INV_QUALS>>; > + > + public: > + using result_type = _Res; > + > + /// Creates an empty object. > + copyable_function() noexcept { } > + > + /// Creates an empty object. > + copyable_function(nullptr_t) noexcept { } > + > + /// Moves the target object, leaving the source empty. > + copyable_function(copyable_function&& __x) noexcept > + : _Base(static_cast<_Base&&>(__x)), > + _M_invoke(std::__exchange(__x._M_invoke, nullptr)) > + { } > + > + /// Copies the target object. > + copyable_function(copyable_function const& __x) > + : _Base(static_cast<const _Base&>(__x)), > + _M_invoke(__x._M_invoke) > + { } > + > + /// Stores a target object initialized from the argument. > + template<typename _Fn, typename _Vt = decay_t<_Fn>> > + requires (!is_same_v<_Vt, copyable_function>) > + && (!__is_in_place_type_v<_Vt>) && __is_callable_from<_Vt> > + copyable_function(_Fn&& __f) noexcept(_S_nothrow_init<_Vt, _Fn>()) > + { > + static_assert(is_copy_constructible_v<_Vt>); > + if constexpr (is_function_v<remove_pointer_t<_Vt>> > + || is_member_pointer_v<_Vt> > + || __is_polymorphic_function_v<_Vt>) > + { > + if (__f == nullptr) > + return; > + } I'd recommend an extra newline between this if and the next one to make it clear that they're not related, and that there's no missing 'else'. > + if constexpr (!__is_polymorphic_function_v<_Vt> > + || !__polyfunc::__is_invoker_convertible<_Vt, > copyable_function>()) > + { > + _M_init<_Vt>(std::forward<_Fn>(__f)); > + _M_invoke = _Invoker::template _S_storage<_Vt > _GLIBCXX_MOF_INV_QUALS>(); > + } > + else if constexpr (is_lvalue_reference_v<_Fn>) > + { > + _M_copy(__polyfunc::__base_of(__f)); > + _M_invoke = __polyfunc::__invoker_of(__f); > + } > + else > + { > + _M_move(__polyfunc::__base_of(__f)); > + _M_invoke = std::__exchange(__polyfunc::__invoker_of(__f), > nullptr); > + } > + } > + > + /// Stores a target object initialized from the arguments. > + template<typename _Tp, typename... _Args> > + requires is_constructible_v<_Tp, _Args...> > + && __is_callable_from<_Tp> > + explicit > + copyable_function(in_place_type_t<_Tp>, _Args&&... __args) > + noexcept(_S_nothrow_init<_Tp, _Args...>()) > + : _M_invoke(_Invoker::template _S_storage<_Tp _GLIBCXX_MOF_INV_QUALS>()) > + { > + static_assert(is_same_v<decay_t<_Tp>, _Tp>); > + static_assert(is_copy_constructible_v<_Tp>); > + _M_init<_Tp>(std::forward<_Args>(__args)...); > + } > + > + /// Stores a target object initialized from the arguments. > + template<typename _Tp, typename _Up, typename... _Args> > + requires is_constructible_v<_Tp, initializer_list<_Up>&, _Args...> > + && __is_callable_from<_Tp> > + explicit > + copyable_function(in_place_type_t<_Tp>, initializer_list<_Up> __il, > + _Args&&... __args) > + noexcept(_S_nothrow_init<_Tp, initializer_list<_Up>&, _Args...>()) > + : _M_invoke(_Invoker::template _S_storage<_Tp _GLIBCXX_MOF_INV_QUALS>()) > + { > + static_assert(is_same_v<decay_t<_Tp>, _Tp>); > + static_assert(is_copy_constructible_v<_Tp>); > + _M_init<_Tp>(__il, std::forward<_Args>(__args)...); > + } > + > + /// Stores a new target object, leaving `x` empty. > + copyable_function& > + operator=(copyable_function&& __x) noexcept > + { > + // Standard requires support of self assigment, by specifying it as > + // copy and swap. > + if (this != addressof(__x)) [[likely]] > + { > + _Base::operator=(static_cast<_Base&&>(__x)); > + _M_invoke = std::__exchange(__x._M_invoke, nullptr); > + } > + return *this; > + } > + > + /// Stores a copy of target object > + copyable_function& > + operator=(const copyable_function& __x) > + { > + copyable_function(__x).swap(*this); > + return *this; > + } > + > + /// Destroys the target object (if any). > + copyable_function& > + operator=(nullptr_t) noexcept > + { > + _M_reset(); > + _M_invoke = nullptr; > + return *this; > + } > + > + /// Stores a new target object, initialized from the argument. > + template<typename _Fn> > + requires is_constructible_v<copyable_function, _Fn> > + copyable_function& > + operator=(_Fn&& __f) > + noexcept(is_nothrow_constructible_v<copyable_function, _Fn>) > + { > + copyable_function(std::forward<_Fn>(__f)).swap(*this); > + return *this; > + } > + > + ~copyable_function() = default; > + > + /// True if a target object is present, false otherwise. > + explicit operator bool() const noexcept > + { return _M_invoke != nullptr; } > + > + /** Invoke the target object. > + * > + * The target object will be invoked using the supplied arguments, > + * and as an lvalue or rvalue, and as const or non-const, as dictated > + * by the template arguments of the `copyable_function` specialization. > + * > + * @pre Must not be empty. > + */ > + _Res > + operator()(_ArgTypes... __args) _GLIBCXX_MOF_CV_REF noexcept(_Noex) > + { > + __glibcxx_assert(*this != nullptr); > + return _M_invoke(this->_M_storage, std::forward<_ArgTypes>(__args)...); > + } > + > + /// Exchange the target objects (if any). > + void > + swap(copyable_function& __x) noexcept > + { > + _Base::swap(__x); > + std::swap(_M_invoke, __x._M_invoke); > + } > + > + /// Exchange the target objects (if any). > + friend void > + swap(copyable_function& __x, copyable_function& __y) noexcept > + { __x.swap(__y); } > + > + /// Check for emptiness by comparing with `nullptr`. > + friend bool > + operator==(const copyable_function& __x, nullptr_t) noexcept > + { return __x._M_invoke == nullptr; } > + > + private: > + typename _Invoker::__storage_func_t _M_invoke = nullptr; > + > + template<typename _Func> > + friend auto& > + __polyfunc::__invoker_of(_Func&) noexcept; > + > + template<typename _Func> > + friend auto& > + __polyfunc::__base_of(_Func&) noexcept; > + > + template<typename _Dst, typename _Src> > + friend consteval bool > + __polyfunc::__is_invoker_convertible() noexcept; Some indentation inconsistencies in these friend declarations > + }; > + > +#undef _GLIBCXX_MOF_CV_REF > +#undef _GLIBCXX_MOF_CV > +#undef _GLIBCXX_MOF_REF > +#undef _GLIBCXX_MOF_INV_QUALS > + > +_GLIBCXX_END_NAMESPACE_VERSION > +} // namespace std > diff --git a/libstdc++-v3/include/bits/mofunc_impl.h > b/libstdc++-v3/include/bits/mofunc_impl.h > index 839f19e0389..68264bd9547 100644 > --- a/libstdc++-v3/include/bits/mofunc_impl.h > +++ b/libstdc++-v3/include/bits/mofunc_impl.h > @@ -101,6 +101,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > && (!__is_in_place_type_v<_Vt>) && __is_callable_from<_Vt> > move_only_function(_Fn&& __f) noexcept(_S_nothrow_init<_Vt, _Fn>()) > { > + // _GLIBCXX_RESOLVE_LIB_DEFECTS > + // 4255. move_only_function constructor should recognize empty > + // copyable_functions > if constexpr (is_function_v<remove_pointer_t<_Vt>> > || is_member_pointer_v<_Vt> > || __is_polymorphic_function_v<_Vt>) > @@ -108,6 +111,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > if (__f == nullptr) > return; > } > + > if constexpr (__is_polymorphic_function_v<_Vt> > && __polyfunc::__is_invoker_convertible<_Vt, > move_only_function>()) > { > diff --git a/libstdc++-v3/include/bits/move_only_function.h > b/libstdc++-v3/include/bits/move_only_function.h > index 5eb688a0ef4..416b0a17ed6 100644 > --- a/libstdc++-v3/include/bits/move_only_function.h > +++ b/libstdc++-v3/include/bits/move_only_function.h > @@ -1,4 +1,4 @@ > -// Implementation of std::move_only_function -*- C++ -*- > +// Implementation of std::move_only_function and std::copyable_function -*- > C++ -*- > > // Copyright The GNU Toolchain Authors. > // > @@ -36,7 +36,7 @@ > > #include <bits/version.h> > > -#ifdef __glibcxx_move_only_function // C++ >= 23 && HOSTED > +#if __cplusplus > 202002L && _GLIBCXX_HOSTED > > #include <bits/invoke.h> > #include <bits/utility.h> > @@ -148,7 +148,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > }; > > // A function that performs operation __op on the __target and possibly > __src. > - using _Func = void (*)(_Op __op, _Storage& __target, const _Storage* > __src) noexcept; > + using _Func = void (*)(_Op __op, _Storage& __target, const _Storage* > __src); > > // The no-op manager function for objects with no target. > static void _S_empty(_Op, _Storage&, const _Storage*) noexcept { } > @@ -356,6 +356,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > { _M_manage(_Manager::_Op::_Destroy, _M_storage, nullptr); } > > _Manager::_Func _M_manage; > + > +#ifdef __glibcxx_copyable_function // C++ >= 26 && HOSTED > + friend class _Cpy_base; > +#endif // __glibcxx_copyable_function > }; > > template<typename _Func> > @@ -381,6 +385,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > } // namespace __polyfunc > /// @endcond > > +#ifdef __glibcxx_move_only_function // C++ >= 23 && HOSTED > template<typename... _Signature> > class move_only_function; // not defined > > @@ -400,10 +405,71 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > { }; > } // namespace __detail::__variant > /// @endcond > +#endif // __glibcxx_move_only_function > + > +#ifdef __glibcxx_copyable_function // C++ >= 26 && HOSTED > + /// @cond undocumented > + namespace __polyfunc > + { > + class _Cpy_base : public _Mo_base > + { > + protected: > + _Cpy_base() = default; > + > + template<typename _Tp, typename... _Args> > + void > + _M_init(_Args&&... __args) > + noexcept(_S_nothrow_init<_Tp, _Args...>()) > + { > + _M_storage._M_init<_Tp>(std::forward<_Args>(__args)...); > + _M_manage = _Manager::_S_select<true, _Tp>(); > + } > + > + void _M_copy(_Cpy_base const& __x) Newline after 'void' LGTM besides that > + { > + using _Op = _Manager::_Op; > + __x._M_manage(_Op::_Copy, _M_storage, &__x._M_storage); > + _M_manage = __x._M_manage; > + } > + > + _Cpy_base(_Cpy_base&&) = default; > + > + _Cpy_base(_Cpy_base const& __x) > + { _M_copy(__x); } > + > + _Cpy_base& > + operator=(_Cpy_base&&) = default; > + > + _Cpy_base& > + // Needs to use copy and swap for exception guarantees. > + operator=(_Cpy_base const&) = delete; > + }; > + } // namespace __polyfunc > + /// @endcond > + > + template<typename... _Signature> > + class copyable_function; // not defined > + > + template<typename _Tp> > + constexpr bool __is_polymorphic_function_v<copyable_function<_Tp>> = > true; > + > + namespace __detail::__variant > + { > + template<typename> struct _Never_valueless_alt; // see <variant> > + > + // Provide the strong exception-safety guarantee when emplacing a > + // copyable_function into a variant. > + template<typename... _Signature> > + struct _Never_valueless_alt<std::copyable_function<_Signature...>> > + : true_type > + { }; > + } // namespace __detail::__variant > +#endif // __glibcxx_copyable_function > > _GLIBCXX_END_NAMESPACE_VERSION > } // namespace std > > +#ifdef __glibcxx_move_only_function // C++ >= 23 && HOSTED > #include "mofunc_impl.h" > #define _GLIBCXX_MOF_CV const > #include "mofunc_impl.h" > @@ -417,6 +483,23 @@ _GLIBCXX_END_NAMESPACE_VERSION > #define _GLIBCXX_MOF_CV const > #define _GLIBCXX_MOF_REF && > #include "mofunc_impl.h" > - > #endif // __glibcxx_move_only_function > + > +#ifdef __glibcxx_copyable_function // C++ >= 26 && HOSTED > +#include "cpyfunc_impl.h" > +#define _GLIBCXX_MOF_CV const > +#include "cpyfunc_impl.h" > +#define _GLIBCXX_MOF_REF & > +#include "cpyfunc_impl.h" > +#define _GLIBCXX_MOF_REF && > +#include "cpyfunc_impl.h" > +#define _GLIBCXX_MOF_CV const > +#define _GLIBCXX_MOF_REF & > +#include "cpyfunc_impl.h" > +#define _GLIBCXX_MOF_CV const > +#define _GLIBCXX_MOF_REF && > +#include "cpyfunc_impl.h" > +#endif // __glibcxx_copyable_function > + > +#endif // __cplusplus > 202002L && _GLIBCXX_HOSTED > #endif // _GLIBCXX_MOVE_ONLY_FUNCTION_H > diff --git a/libstdc++-v3/include/bits/version.def > b/libstdc++-v3/include/bits/version.def > index 2d34a8dff7f..21f16e26ba4 100644 > --- a/libstdc++-v3/include/bits/version.def > +++ b/libstdc++-v3/include/bits/version.def > @@ -1747,6 +1747,16 @@ ftms = { > }; > }; > > +ftms = { > + name = copyable_function; > + values = { > + v = 202306; > + cxxmin = 26; > + hosted = yes; > + }; > +}; > + > + > ftms = { > name = out_ptr; > values = { > diff --git a/libstdc++-v3/include/bits/version.h > b/libstdc++-v3/include/bits/version.h > index 24831f70b41..48a090c14a3 100644 > --- a/libstdc++-v3/include/bits/version.h > +++ b/libstdc++-v3/include/bits/version.h > @@ -1948,6 +1948,16 @@ > #endif /* !defined(__cpp_lib_move_only_function) && > defined(__glibcxx_want_move_only_function) */ > #undef __glibcxx_want_move_only_function > > +#if !defined(__cpp_lib_copyable_function) > +# if (__cplusplus > 202302L) && _GLIBCXX_HOSTED > +# define __glibcxx_copyable_function 202306L > +# if defined(__glibcxx_want_all) || > defined(__glibcxx_want_copyable_function) > +# define __cpp_lib_copyable_function 202306L > +# endif > +# endif > +#endif /* !defined(__cpp_lib_copyable_function) && > defined(__glibcxx_want_copyable_function) */ > +#undef __glibcxx_want_copyable_function > + > #if !defined(__cpp_lib_out_ptr) > # if (__cplusplus >= 202100L) > # define __glibcxx_out_ptr 202311L > diff --git a/libstdc++-v3/include/std/functional > b/libstdc++-v3/include/std/functional > index 1077e9678d1..46179998eeb 100644 > --- a/libstdc++-v3/include/std/functional > +++ b/libstdc++-v3/include/std/functional > @@ -80,6 +80,7 @@ > #define __glibcxx_want_bind_front > #define __glibcxx_want_bind_back > #define __glibcxx_want_constexpr_functional > +#define __glibcxx_want_copyable_function > #define __glibcxx_want_invoke > #define __glibcxx_want_invoke_r > #define __glibcxx_want_move_only_function > diff --git a/libstdc++-v3/testsuite/20_util/copyable_function/call.cc > b/libstdc++-v3/testsuite/20_util/copyable_function/call.cc > new file mode 100644 > index 00000000000..cf997577f62 > --- /dev/null > +++ b/libstdc++-v3/testsuite/20_util/copyable_function/call.cc > @@ -0,0 +1,224 @@ > +// { dg-do run { target c++26 } } > +// { dg-require-effective-target hosted } > + > +#include <functional> > +#include <utility> > +#include <testsuite_hooks.h> > + > +using std::copyable_function; > + > +using std::is_same_v; > +using std::is_invocable_v; > +using std::is_nothrow_invocable_v; > +using std::invoke_result_t; > + > +// Check return types > +static_assert( is_same_v<void, invoke_result_t<copyable_function<void()>>> ); > +static_assert( is_same_v<int, invoke_result_t<copyable_function<int()>>> ); > +static_assert( is_same_v<int&, invoke_result_t<copyable_function<int&()>>> ); > + > +// With const qualifier > +static_assert( ! is_invocable_v< copyable_function<void()> const > ); > +static_assert( ! is_invocable_v< copyable_function<void()> const &> ); > +static_assert( is_invocable_v< copyable_function<void() const> > ); > +static_assert( is_invocable_v< copyable_function<void() const> &> ); > +static_assert( is_invocable_v< copyable_function<void() const> const > ); > +static_assert( is_invocable_v< copyable_function<void() const> const &> ); > + > +// With no ref-qualifier > +static_assert( is_invocable_v< copyable_function<void()> > ); > +static_assert( is_invocable_v< copyable_function<void()> &> ); > +static_assert( is_invocable_v< copyable_function<void() const> > ); > +static_assert( is_invocable_v< copyable_function<void() const> &> ); > +static_assert( is_invocable_v< copyable_function<void() const> const > ); > +static_assert( is_invocable_v< copyable_function<void() const> const &> ); > + > +// With & ref-qualifier > +static_assert( ! is_invocable_v< copyable_function<void()&> > ); > +static_assert( is_invocable_v< copyable_function<void()&> &> ); > +static_assert( is_invocable_v< copyable_function<void() const&> > ); > +static_assert( is_invocable_v< copyable_function<void() const&> &> ); > +static_assert( is_invocable_v< copyable_function<void() const&> const > ); > +static_assert( is_invocable_v< copyable_function<void() const&> const &> ); > + > +// With && ref-qualifier > +static_assert( is_invocable_v< copyable_function<void()&&> > ); > +static_assert( ! is_invocable_v< copyable_function<void()&&> &> ); > +static_assert( is_invocable_v< copyable_function<void() const&&> > ); > +static_assert( ! is_invocable_v< copyable_function<void() const&&> &> ); > +static_assert( is_invocable_v< copyable_function<void() const&&> const > ); > +static_assert( ! is_invocable_v< copyable_function<void() const&&> const &> > ); > + > +// With noexcept-specifier > +static_assert( ! is_nothrow_invocable_v< copyable_function<void()> > ); > +static_assert( ! is_nothrow_invocable_v< copyable_function<void() > noexcept(false)> > ); > +static_assert( is_nothrow_invocable_v< copyable_function<void() noexcept> > > ); > +static_assert( is_nothrow_invocable_v< copyable_function<void()& noexcept>& > > ); > + > +void > +test01() > +{ > + struct F > + { > + int operator()() { return 0; } > + int operator()() const { return 1; } > + }; > + > + copyable_function<int()> f0{F{}}; > + VERIFY( f0() == 0 ); > + VERIFY( std::move(f0)() == 0 ); > + > + copyable_function<int() const> f1{F{}}; > + VERIFY( f1() == 1 ); > + VERIFY( std::as_const(f1)() == 1 ); > + VERIFY( std::move(f1)() == 1 ); > + VERIFY( std::move(std::as_const(f1))() == 1 ); > + > + copyable_function<int()&> f2{F{}}; > + VERIFY( f2() == 0 ); > + // Not rvalue-callable: std::move(f2)() > + > + copyable_function<int() const&> f3{F{}}; > + VERIFY( f3() == 1 ); > + VERIFY( std::as_const(f3)() == 1 ); > + VERIFY( std::move(f3)() == 1 ); > + VERIFY( std::move(std::as_const(f3))() == 1 ); > + > + copyable_function<int()&&> f4{F{}}; > + // Not lvalue-callable: f4() > + VERIFY( std::move(f4)() == 0 ); > + > + copyable_function<int() const&&> f5{F{}}; > + // Not lvalue-callable: f5() > + VERIFY( std::move(f5)() == 1 ); > + VERIFY( std::move(std::as_const(f5))() == 1 ); > +} > + > +void > +test02() > +{ > + struct F > + { > + int operator()() & { return 0; } > + int operator()() && { return 1; } > + }; > + > + copyable_function<int()> f0{F{}}; > + VERIFY( f0() == 0 ); > + VERIFY( std::move(f0)() == 0 ); > + > + copyable_function<int()&&> f1{F{}}; > + // Not lvalue callable: f1() > + VERIFY( std::move(f1)() == 1 ); > + > + copyable_function<int()&> f2{F{}}; > + VERIFY( f2() == 0 ); > + // Not rvalue-callable: std::move(f2)() > +} > + > +void > +test03() > +{ > + struct F > + { > + int operator()() const & { return 0; } > + int operator()() && { return 1; } > + }; > + > + copyable_function<int()> f0{F{}}; > + VERIFY( f0() == 0 ); > + VERIFY( std::move(f0)() == 0 ); > + > + copyable_function<int()&&> f1{F{}}; > + // Not lvalue callable: f1() > + VERIFY( std::move(f1)() == 1 ); > + > + copyable_function<int() const> f2{F{}}; > + VERIFY( f2() == 0 ); > + VERIFY( std::as_const(f2)() == 0 ); > + VERIFY( std::move(f2)() == 0 ); > + VERIFY( std::move(std::as_const(f2))() == 0 ); > + > + copyable_function<int() const &&> f3{F{}}; > + // Not lvalue callable: f3() > + VERIFY( std::move(f3)() == 0 ); > + VERIFY( std::move(std::as_const(f3))() == 0 ); > + > + copyable_function<int() const &> f4{F{}}; > + VERIFY( f4() == 0 ); > + VERIFY( std::as_const(f4)() == 0 ); > + // Not rvalue-callable: std::move(f4)() > +} > + > +void > +test04() > +{ > + struct F > + { > + int operator()() & { return 0; } > + int operator()() && { return 1; } > + int operator()() const & { return 2; } > + int operator()() const && { return 3; } > + }; > + > + copyable_function<int()> f0{F{}}; > + VERIFY( f0() == 0 ); > + VERIFY( std::move(f0)() == 0 ); > + > + copyable_function<int()&> f1{F{}}; > + VERIFY( f1() == 0 ); > + // Not rvalue-callable: std::move(f1)() > + > + copyable_function<int()&&> f2{F{}}; > + // Not lvalue callable: f2() > + VERIFY( std::move(f2)() == 1 ); > + > + copyable_function<int() const> f3{F{}}; > + VERIFY( f3() == 2 ); > + VERIFY( std::as_const(f3)() == 2 ); > + VERIFY( std::move(f3)() == 2 ); > + VERIFY( std::move(std::as_const(f3))() == 2 ); > + > + copyable_function<int() const &> f4{F{}}; > + VERIFY( f4() == 2 ); > + VERIFY( std::as_const(f4)() == 2 ); > + // Not rvalue-callable: std::move(f4)() > + > + copyable_function<int() const &&> f5{F{}}; > + // Not lvalue callable: f5() > + VERIFY( std::move(f5)() == 3 ); > + VERIFY( std::move(std::as_const(f5))() == 3 ); > +} > + > +void > +test05() > +{ > + int (*fp)() = [] { return 0; }; > + copyable_function<int()> f0{fp}; > + VERIFY( f0() == 0 ); > + VERIFY( std::move(f0)() == 0 ); > + > + const copyable_function<int() const> f1{fp}; > + VERIFY( f1() == 0 ); > + VERIFY( std::move(f1)() == 0 ); > +} > + > +struct Incomplete; > + > +void > +test_params() > +{ > + std::copyable_function<void(Incomplete)> f1; > + std::copyable_function<void(Incomplete&)> f2; > + std::copyable_function<void(Incomplete&&)> f3; > +} > + > +int main() > +{ > + test01(); > + test02(); > + test03(); > + test04(); > + test05(); > + test_params(); > +} > diff --git a/libstdc++-v3/testsuite/20_util/copyable_function/cons.cc > b/libstdc++-v3/testsuite/20_util/copyable_function/cons.cc > new file mode 100644 > index 00000000000..8d422dcff8a > --- /dev/null > +++ b/libstdc++-v3/testsuite/20_util/copyable_function/cons.cc > @@ -0,0 +1,126 @@ > +// { dg-do compile { target c++26 } } > +// { dg-require-effective-target hosted } > +// { dg-add-options no_pch } > + > +#include <functional> > + > +#ifndef __cpp_lib_copyable_function > +# error "Feature-test macro for copyable_function missing in <functional>" > +#elif __cpp_lib_copyable_function != 202306L > +# error "Feature-test macro for copyable_function has wrong value in > <functional>" > +#endif > + > +using std::copyable_function; > + > +using std::is_constructible_v; > +using std::is_copy_constructible_v; > +using std::is_nothrow_default_constructible_v; > +using std::is_nothrow_move_constructible_v; > +using std::is_nothrow_constructible_v; > +using std::nullptr_t; > +using std::in_place_type_t; > + > +static_assert( is_nothrow_default_constructible_v<copyable_function<void()>> > ); > +static_assert( is_nothrow_constructible_v<copyable_function<void()>, > nullptr_t> ); > +static_assert( is_nothrow_move_constructible_v<copyable_function<void()>> ); > +static_assert( is_copy_constructible_v<copyable_function<void()>> ); > + > +static_assert( is_constructible_v<copyable_function<void()>, void()> ); > +static_assert( is_constructible_v<copyable_function<void()>, void(&)()> ); > +static_assert( is_constructible_v<copyable_function<void()>, void(*)()> ); > +static_assert( is_constructible_v<copyable_function<void()>, int()> ); > +static_assert( is_constructible_v<copyable_function<void()>, int(&)()> ); > +static_assert( is_constructible_v<copyable_function<void()>, int(*)()> ); > +static_assert( ! is_constructible_v<copyable_function<void()>, void(int)> ); > +static_assert( is_constructible_v<copyable_function<void(int)>, void(int)> ); > + > +static_assert( is_constructible_v<copyable_function<void(int)>, > + in_place_type_t<void(*)(int)>, void(int)> ); > + > +static_assert( is_constructible_v<copyable_function<void()>, > + void() noexcept> ); > +static_assert( is_constructible_v<copyable_function<void() noexcept>, > + void() noexcept> ); > +static_assert( ! is_constructible_v<copyable_function<void() noexcept>, > + void() > ); > + > +struct Q > +{ > + void operator()() const &; > + void operator()() &&; > +}; > + > +static_assert( is_constructible_v<copyable_function<void()>, Q> ); > +static_assert( is_constructible_v<copyable_function<void() const>, Q> ); > +static_assert( is_constructible_v<copyable_function<void() &>, Q> ); > +static_assert( is_constructible_v<copyable_function<void() const &>, Q> ); > +static_assert( is_constructible_v<copyable_function<void() &&>, Q> ); > +static_assert( is_constructible_v<copyable_function<void() const &&>, Q> ); > + > +struct R > +{ > + void operator()() &; > + void operator()() &&; > +}; > + > +static_assert( is_constructible_v<copyable_function<void()>, R> ); > +static_assert( is_constructible_v<copyable_function<void()&>, R> ); > +static_assert( is_constructible_v<copyable_function<void()&&>, R> ); > +static_assert( ! is_constructible_v<copyable_function<void() const>, R> ); > +static_assert( ! is_constructible_v<copyable_function<void() const&>, R> ); > +static_assert( ! is_constructible_v<copyable_function<void() const&&>, R> ); > + > +// The following nothrow-constructible guarantees are a GCC extension, > +// not required by the standard. > + > +static_assert( is_nothrow_constructible_v<copyable_function<void()>, void()> > ); > +static_assert( is_nothrow_constructible_v<copyable_function<void(int)>, > + in_place_type_t<void(*)(int)>, > + void(int)> ); > + > +// These types are all small and nothrow move constructible > +struct F { void operator()(); }; > +struct G { void operator()() const; }; > +static_assert( is_nothrow_constructible_v<copyable_function<void()>, F> ); > +static_assert( is_nothrow_constructible_v<copyable_function<void()>, G> ); > +static_assert( is_nothrow_constructible_v<copyable_function<void() const>, > G> ); > + > +struct H { > + H(int); > + H(int, int) noexcept; > + void operator()() noexcept; > +}; > +static_assert( is_nothrow_constructible_v<copyable_function<void()>, H> ); > +static_assert( is_nothrow_constructible_v<copyable_function<void() noexcept>, > + H> ); > +static_assert( ! is_nothrow_constructible_v<copyable_function<void() > noexcept>, > + in_place_type_t<H>, int> ); > +static_assert( is_nothrow_constructible_v<copyable_function<void() noexcept>, > + in_place_type_t<H>, int, int> ); > + > +struct I { > + I(int, const char*); > + I(std::initializer_list<char>); > + int operator()() const noexcept; > +}; > + > +static_assert( is_constructible_v<copyable_function<void()>, > + std::in_place_type_t<I>, > + int, const char*> ); > +static_assert( is_constructible_v<copyable_function<void()>, > + std::in_place_type_t<I>, > + std::initializer_list<char>> ); > + > +void > +test_instantiation() > +{ > + // Instantiate the constructor bodies > + copyable_function<void()> f0; > + copyable_function<void()> f1(nullptr); > + copyable_function<void()> f2( I(1, "two") ); > + copyable_function<void()> f3(std::in_place_type<I>, 3, "four"); > + copyable_function<void()> f4(std::in_place_type<I>, // PR libstdc++/102825 > + { 'P', 'R', '1', '0', '2', '8', '2', '5'}); > + auto f5 = std::move(f4); > + f4 = std::move(f5); > +} > diff --git a/libstdc++-v3/testsuite/20_util/copyable_function/conv.cc > b/libstdc++-v3/testsuite/20_util/copyable_function/conv.cc > new file mode 100644 > index 00000000000..0f1b163a56a > --- /dev/null > +++ b/libstdc++-v3/testsuite/20_util/copyable_function/conv.cc > @@ -0,0 +1,253 @@ > +// { dg-do run { target c++26 } } > +// { dg-require-effective-target hosted } > + > +#include <functional> > +#include <testsuite_hooks.h> > + > +using std::copyable_function; > + > +static_assert( !std::is_constructible_v<std::copyable_function<void()>, > + std::copyable_function<void()&>> ); > +static_assert( !std::is_constructible_v<std::copyable_function<void()>, > + std::copyable_function<void()&&>> ); > +static_assert( !std::is_constructible_v<std::copyable_function<void()&>, > + std::copyable_function<void()&&>> ); > +static_assert( !std::is_constructible_v<std::copyable_function<void() const>, > + std::copyable_function<void()>> ); > + > +// Non-trivial args, guarantess that type is not passed by copy > +struct CountedArg > +{ > + CountedArg() = default; > + CountedArg(const CountedArg& f) noexcept : counter(f.counter) { ++counter; > } > + CountedArg& operator=(CountedArg&&) = delete; > + > + int counter = 0; > +}; > +CountedArg const c; > + > +// When copyable_function or move_only_function is constructed from other > copyable_function, > +// the compiler can avoid double indirection per C++26 [func.wrap.general] > p2. > + > +void > +test01() > +{ > + auto f = [](CountedArg const& arg) noexcept { return arg.counter; }; > + std::copyable_function<int(CountedArg) const noexcept> c1(f); > + using CF = std::copyable_function<int(CountedArg) const noexcept>; > + VERIFY( c1(c) == 1 ); > + > + std::copyable_function<int(CountedArg) const> c2a(c1); > + VERIFY( c2a(c) == 1 ); > + > + std::copyable_function<int(CountedArg) const> c2b(static_cast<CF>(c1)); > + VERIFY( c2b(c) == 1 ); > + > + std::move_only_function<int(CountedArg) const> m2a(c1); > + VERIFY( m2a(c) == 1 ); > + > + std::move_only_function<int(CountedArg) const> m2b(static_cast<CF>(c1)); > + VERIFY( m2b(c) == 1 ); > + > + std::copyable_function<int(CountedArg)> c3a(c1); > + VERIFY( c3a(c) == 1 ); > + > + std::copyable_function<int(CountedArg)> c3b(static_cast<CF>(c1)); > + VERIFY( c3b(c) == 1 ); > + > + std::move_only_function<int(CountedArg)> m3a(c1); > + VERIFY( m3a(c) == 1 ); > + > + std::move_only_function<int(CountedArg)> m3b(static_cast<CF>(c1)); > + VERIFY( m3b(c) == 1 ); > + > + // Invokers internally uses Counted&& for non-trivial types, > + // sinature remain compatible. > + std::copyable_function<int(CountedArg&&)> c4a(c1); > + VERIFY( c4a({}) == 0 ); > + > + std::copyable_function<int(CountedArg&&)> c4b(static_cast<CF>(c1)); > + VERIFY( c4b({}) == 0 ); > + > + std::move_only_function<int(CountedArg&&)> m4a(c1); > + VERIFY( m4a({}) == 0 ); > + > + std::move_only_function<int(CountedArg&&)> m4b(static_cast<CF>(c1)); > + VERIFY( m4b({}) == 0 ); > + > + std::copyable_function<int(CountedArg&&)&> c5a(c1); > + VERIFY( c5a({}) == 0 ); > + > + std::copyable_function<int(CountedArg&&)&&> c5b(static_cast<CF>(c1)); > + VERIFY( std::move(c5b)({}) == 0 ); > + > + std::move_only_function<int(CountedArg&&)&> m5a(c1); > + VERIFY( m5a({}) == 0 ); > + > + std::move_only_function<int(CountedArg&&)&&> m5b(static_cast<CF>(c1)); > + VERIFY( std::move(m5b)({}) == 0 ); > + > + // Incompatible signatures > + std::copyable_function<long(CountedArg) const noexcept> c6a(c1); > + VERIFY( c6a(c) == 2 ); > + > + std::copyable_function<long(CountedArg) const noexcept> > c6b(static_cast<CF>(c1)); > + VERIFY( c6b(c) == 2 ); > + > + std::move_only_function<long(CountedArg) const noexcept> m6a(c1); > + VERIFY( m6a(c) == 2 ); > + > + std::move_only_function<long(CountedArg) const noexcept> > m6b(static_cast<CF>(c1)); > + VERIFY( m6b(c) == 2 ); > +} > + > +void > +test02() > +{ > + auto f = [](CountedArg const& arg) noexcept { return arg.counter; }; > + std::copyable_function<int(CountedArg) const noexcept> c1(f); > + using CF = std::copyable_function<int(CountedArg) const noexcept>; > + VERIFY( c1(c) == 1 ); > + > + std::copyable_function<int(CountedArg) const> c2; > + c2 = c1; > + VERIFY( c2(c) == 1 ); > + c2 = static_cast<CF>(c1); > + VERIFY( c2(c) == 1 ); > + > + std::move_only_function<int(CountedArg) const> m2; > + m2 = c1; > + VERIFY( m2(c) == 1 ); > + m2 = static_cast<CF>(c1); > + VERIFY( m2(c) == 1 ); > + > + // Incompatible signatures > + std::copyable_function<long(CountedArg) const noexcept> c3; > + c3 = c1; > + VERIFY( c3(c) == 2 ); > + c3 = static_cast<CF>(c1); > + VERIFY( c3(c) == 2 ); > + > + std::move_only_function<long(CountedArg) const noexcept> m3; > + m3 = c1; > + VERIFY( m3(c) == 2 ); > + m3 = static_cast<CF>(c1); > + VERIFY( m3(c) == 2 ); > +} > + > +void > +test03() > +{ > + std::copyable_function<int(long) const noexcept> c1; > + VERIFY( c1 == nullptr ); > + > + std::copyable_function<int(long) const> c2(c1); > + VERIFY( c2 == nullptr ); > + c2 = c1; > + VERIFY( c2 == nullptr ); > + c2 = std::move(c1); > + VERIFY( c2 == nullptr ); > + > + std::copyable_function<bool(int) const> c3(std::move(c1)); > + VERIFY( c3 == nullptr ); > + c3 = c1; > + VERIFY( c3 == nullptr ); > + c3 = std::move(c1); > + VERIFY( c3 == nullptr ); > + > + // Standard specifies this as move_only_function containing > + // empty copyable_function, i.e. they are not equal nullptr. > + // Invoking call operator remains UB, however we cannot > + // is UB, but it cannot be checked on user side. > + std::move_only_function<int(long) const noexcept> m1(c1); > + VERIFY( m1 == nullptr ); > + m1 = c1; > + VERIFY( m1 == nullptr ); > + m1 = std::move(c1); > + VERIFY( m1 == nullptr ); > + > + std::move_only_function<int(long) const> m2(c1); > + VERIFY( m2 == nullptr ); > + m2 = c1; > + VERIFY( m2 == nullptr ); > + m2 = std::move(c1); > + VERIFY( m2 == nullptr ); > + > + std::move_only_function<bool(int) const> m3(std::move(c1)); > + VERIFY( m3 == nullptr ); > + m3 = c1; > + VERIFY( m3 == nullptr ); > + m3 = std::move(c1); > + VERIFY( m3 == nullptr ); > +} > + > +void > +test04() > +{ > + struct F > + { > + int operator()(CountedArg const& arg) noexcept > + { return arg.counter; } > + > + int operator()(CountedArg const& arg) const noexcept > + { return arg.counter + 1000; } > + }; > + > + F f; > + std::copyable_function<int(CountedArg) const> c1(f); > + VERIFY( c1(c) == 1001 ); > + > + // Call const overload as std::copyable_function<int(CountedArg) const> > + // inside td::copyable_function<int(CountedArg)> would do. > + std::copyable_function<int(CountedArg)> c2(c1); > + VERIFY( c2(c) == 1001 ); > + std::move_only_function<int(CountedArg)> m2(c1); > + VERIFY( m2(c) == 1001 ); > + > + std::copyable_function<int(CountedArg)> m3(f); > + VERIFY( m3(c) == 1 ); > +} > + > +void > +test05() > +{ > + auto f = [](CountedArg const& arg) noexcept { return arg.counter; }; > + std::copyable_function<int(CountedArg)> w1(f); > + // copyable_function stores copyable_function due incompatibile signatures > + std::copyable_function<int(CountedArg const&)> w2(std::move(w1)); > + // copy is made when passing to int(CountedArg) > + VERIFY( w2(c) == 1 ); > + // wrapped 3 times > + w1 = std::move(w2); > + VERIFY( w1(c) == 2 ); > + // wrapped 4 times > + w2 = std::move(w1); > + VERIFY( w2(c) == 2 ); > + // wrapped 5 times > + w1 = std::move(w2); > + VERIFY( w1(c) == 3 ); > +} > + > +void > +test06() > +{ > + // No special interoperability with std::function > + auto f = [](CountedArg const& arg) noexcept { return arg.counter; }; > + std::function<int(CountedArg)> f1(f); > + std::copyable_function<int(CountedArg) const> c1(std::move(f1)); > + VERIFY( c1(c) == 2 ); > + > + std::copyable_function<int(CountedArg) const> c2(f); > + std::function<int(CountedArg)> f2(c2); > + VERIFY( f2(c) == 2 ); > +} > + > +int main() > +{ > + test01(); > + test02(); > + test03(); > + test04(); > + test05(); > + test06(); > +} > diff --git a/libstdc++-v3/testsuite/20_util/copyable_function/copy.cc > b/libstdc++-v3/testsuite/20_util/copyable_function/copy.cc > new file mode 100644 > index 00000000000..6445a272b79 > --- /dev/null > +++ b/libstdc++-v3/testsuite/20_util/copyable_function/copy.cc > @@ -0,0 +1,154 @@ > +// { dg-do run { target c++26 } } > +// { dg-require-effective-target hosted } > + > +#include <functional> > +#include <testsuite_hooks.h> > + > +using std::copyable_function; > + > +void > +test01() > +{ > + // Small type with non-throwing move constructor. Not allocated on the > heap. > + struct F > + { > + F() = default; > + F(const F& f) : counters(f.counters) { ++counters.copy; } > + F(F&& f) noexcept : counters(f.counters) { ++counters.move; } > + > + F& operator=(F&&) = delete; > + > + struct Counters > + { > + int copy = 0; > + int move = 0; > + } counters; > + > + const Counters& operator()() const { return counters; } > + }; > + > + F f; > + std::copyable_function<const F::Counters&() const> m1(f); > + VERIFY( m1().copy == 1 ); > + VERIFY( m1().move == 0 ); > + > + // This will copy construct a new target object > + auto m2 = m1; > + VERIFY( m1 != nullptr && m2 != nullptr ); > + VERIFY( m2().copy == 2 ); > + VERIFY( m2().move == 0 ); > + > + m1 = m2; > + VERIFY( m1 != nullptr && m2 != nullptr ); > + VERIFY( m1().copy == 3 ); > + VERIFY( m1().move == 1 ); // Copies object first and then swaps > + > + m1 = m1; > + VERIFY( m1 != nullptr && m2 != nullptr ); > + VERIFY( m1().copy == 4 ); > + VERIFY( m1().move == 2 ); // Copies object first and then swaps > + > + m2 = f; > + VERIFY( m2().copy == 1 ); > + VERIFY( m2().move == 1 ); // Copy construct target object, then swap into > m2. > +} > + > +void > +test02() > +{ > + // Move constructor is potentially throwing. Allocated on the heap. > + struct F > + { > + F() = default; > + F(const F& f) noexcept : counters(f.counters) { ++counters.copy; } > + F(F&& f) noexcept(false) : counters(f.counters) { ++counters.move; } > + > + F& operator=(F&&) = delete; > + > + struct Counters > + { > + int copy = 0; > + int move = 0; > + } counters; > + > + Counters operator()() const noexcept { return counters; } > + }; > + > + F f; > + std::copyable_function<F::Counters() const> m1(f); > + VERIFY( m1().copy == 1 ); > + VERIFY( m1().move == 0 ); > + > + // The target object is on the heap, but we need to allocate new one > + auto m2 = m1; > + VERIFY( m1 != nullptr && m2 != nullptr ); > + VERIFY( m2().copy == 2 ); > + VERIFY( m2().move == 0 ); > + > + m1 = m2; > + VERIFY( m1 != nullptr && m2 != nullptr ); > + VERIFY( m1().copy == 3 ); > + VERIFY( m1().move == 0 ); > + > + m1 = m1; > + VERIFY( m1 != nullptr && m2 != nullptr ); > + VERIFY( m1().copy == 4 ); > + VERIFY( m1().move == 0 ); > + > + m2 = f; > + VERIFY( m2().copy == 1 ); > + VERIFY( m2().move == 0 ); > +} > + > +void > +test03() > +{ > + // Small type with non-throwing, but not non-trivial move constructor. > + struct F > + { > + F(int i) noexcept : id(i) {} > + F(const F& f) : id(f.id) > + { if (id == 3) throw id; } > + F(F&& f) noexcept : id(f.id) { } > + > + int operator()() const > + { return id; } > + > + int id; > + }; > + > + std::copyable_function<int() const> m1(std::in_place_type<F>, 1); > + const std::copyable_function<int() const> m2(std::in_place_type<F>, 2); > + const std::copyable_function<int() const> m3(std::in_place_type<F>, 3); > + > + try > + { > + auto mc = m3; > + VERIFY( false ); > + } > + catch(int i) > + { > + VERIFY( i == 3 ); > + } > + > + m1 = m2; > + VERIFY( m1() == 2 ); > + > + try > + { > + m1 = m3; > + VERIFY( false ); > + } > + catch (int i) > + { > + VERIFY( i == 3 ); > + } > + VERIFY( m1() == 2 ); > +} > + > +int main() > +{ > + test01(); > + test02(); > + test03(); > +} > diff --git a/libstdc++-v3/testsuite/20_util/copyable_function/move.cc > b/libstdc++-v3/testsuite/20_util/copyable_function/move.cc > new file mode 100644 > index 00000000000..ec9d0d1af92 > --- /dev/null > +++ b/libstdc++-v3/testsuite/20_util/copyable_function/move.cc > @@ -0,0 +1,120 @@ > +// { dg-do run { target c++26 } } > +// { dg-require-effective-target hosted } > + > +#include <functional> > +#include <testsuite_hooks.h> > + > +using std::copyable_function; > + > +void > +test01() > +{ > + // Small type with non-throwing move constructor. Not allocated on the > heap. > + struct F > + { > + F() = default; > + F(const F& f) : counters(f.counters) { ++counters.copy; } > + F(F&& f) noexcept : counters(f.counters) { ++counters.move; } > + > + F& operator=(F&&) = delete; > + > + struct Counters > + { > + int copy = 0; > + int move = 0; > + } counters; > + > + const Counters& operator()() const { return counters; } > + }; > + > + F f; > + std::copyable_function<const F::Counters&() const> m1(f); > + VERIFY( m1().copy == 1 ); > + VERIFY( m1().move == 0 ); > + > + // Standard specifies move assigment as copy and swap > + m1 = std::move(m1); > + VERIFY( m1 != nullptr ); > + VERIFY( m1().copy == 1 ); > + VERIFY( m1().move == 0 ); > + > + // This will move construct a new target object and destroy the old one: > + auto m2 = std::move(m1); > + VERIFY( m1 == nullptr && m2 != nullptr ); > + VERIFY( m2().copy == 1 ); > + VERIFY( m2().move == 1 ); > + > + m1 = std::move(m2); > + VERIFY( m1 != nullptr && m2 == nullptr ); > + VERIFY( m1().copy == 1 ); > + VERIFY( m1().move == 2 ); > + > + m2 = std::move(f); > + VERIFY( m2().copy == 0 ); > + VERIFY( m2().move == 2 ); // Move construct target object, then swap into > m2. > + const int moves = m1().move + m2().move; > + // This will do three moves: > + swap(m1, m2); > + VERIFY( m1().copy == 0 ); > + VERIFY( m2().copy == 1 ); > + VERIFY( (m1().move + m2().move) == (moves + 3) ); > +} > + > +void > +test02() > +{ > + // Move constructor is potentially throwing. Allocated on the heap. > + struct F > + { > + F() = default; > + F(const F& f) noexcept : counters(f.counters) { ++counters.copy; } > + F(F&& f) noexcept(false) : counters(f.counters) { ++counters.move; } > + > + F& operator=(F&&) = delete; > + > + struct Counters > + { > + int copy = 0; > + int move = 0; > + } counters; > + > + Counters operator()() const noexcept { return counters; } > + }; > + > + F f; > + std::copyable_function<F::Counters() const> m1(f); > + VERIFY( m1().copy == 1 ); > + VERIFY( m1().move == 0 ); > + > + m1 = std::move(m1); > + VERIFY( m1 != nullptr ); > + VERIFY( m1().copy == 1 ); > + VERIFY( m1().move == 0 ); > + > + // The target object is on the heap so this just moves a pointer: > + auto m2 = std::move(m1); > + VERIFY( m1 == nullptr && m2 != nullptr ); > + VERIFY( m2().copy == 1 ); > + VERIFY( m2().move == 0 ); > + > + m1 = std::move(m2); > + VERIFY( m1 != nullptr && m2 == nullptr ); > + VERIFY( m1().copy == 1 ); > + VERIFY( m1().move == 0 ); > + > + m2 = std::move(f); > + VERIFY( m2().copy == 0 ); > + VERIFY( m2().move == 1 ); > + const int moves = m1().move + m2().move; > + // This just swaps the pointers, so no moves: > + swap(m1, m2); > + VERIFY( m1().copy == 0 ); > + VERIFY( m2().copy == 1 ); > + VERIFY( (m1().move + m2().move) == moves ); > +} > + > +int main() > +{ > + test01(); > + test02(); > +} > -- > 2.49.0 > >