On Thu, 29 May 2025 at 07:49, Tomasz Kamiński <tkami...@redhat.com> wrote: > > This patch adjust the passing of parameters for the move_only_function, > copyable_function and function_ref. For types that are declared as being > passed > by value in signature template argument, they are passed by value to the > invoker, > when they are small (at most two pointers), trivially move constructible and > trivially destructible. The latter guarantees that passing them by value has > not > user visible side effects. > > In particular, this extends the set of types forwarded by value, that was > previously limited to scalars, to also include specializations of std::span > and > std::string_view, and similar standard and program defined-types. > > Checking the suitability of the parameter types requires the types to be > complete. > As a consequence, the implementation imposes requirements on instantiation of > move_only_function and copyable_function. To avoid producing the errors from > the implementation details, a static assertion was added to partial > specializations of copyable_function, move_only_function and function_ref. > The static assertion uses existing __is_complete_or_unbounded, as arrays type > parameters are automatically decayed in function type. > > Standard already specifies in [res.on.functions] p2.5 that instantiating these > partial specialization with incomplete types leads to undefined behavior. > > libstdc++-v3/ChangeLog: > > * include/bits/funcwrap.h (__polyfunc::__pass_by_rref): Define. > (__polyfunc::__param_t): Update to use __pass_by_rref. > * include/bits/cpyfunc_impl.h:: Assert that are parameters type > are complete. > * include/bits/funcref_impl.h: Likewise. > * include/bits/mofunc_impl.h: Likewise. > * testsuite/20_util/copyable_function/call.cc: New test. > * testsuite/20_util/function_ref/call.cc: New test. > * testsuite/20_util/move_only_function/call.cc: New test. > * testsuite/20_util/copyable_function/conv.cc: New test. > * testsuite/20_util/function_ref/conv.cc: New test. > * testsuite/20_util/move_only_function/conv.cc: New test. > * testsuite/20_util/copyable_function/incomplete_neg.cc: New test. > * testsuite/20_util/function_ref/incomplete_neg.cc: New test. > * testsuite/20_util/move_only_function/incomplete_neg.cc: New test. > > Reviewed-by: Patrick Palka <ppa...@redhat.com> > --- > Changes in v3: > - reworked pass_by_rref to pass_by_value, and simplified impl > - fixed some typos in the commit message > > OK for trunk?
OK, thanks. > > libstdc++-v3/include/bits/cpyfunc_impl.h | 4 +++ > libstdc++-v3/include/bits/funcref_impl.h | 4 +++ > libstdc++-v3/include/bits/funcwrap.h | 20 ++++++++++- > libstdc++-v3/include/bits/mofunc_impl.h | 4 +++ > .../20_util/copyable_function/call.cc | 7 ++-- > .../20_util/copyable_function/conv.cc | 35 +++++++++++++++++++ > .../copyable_function/incomplete_neg.cc | 18 ++++++++++ > .../testsuite/20_util/function_ref/call.cc | 10 +++--- > .../testsuite/20_util/function_ref/conv.cc | 34 ++++++++++++++++++ > .../20_util/function_ref/incomplete_neg.cc | 18 ++++++++++ > .../20_util/move_only_function/call.cc | 7 ++-- > .../20_util/move_only_function/conv.cc | 35 +++++++++++++++++++ > .../move_only_function/incomplete_neg.cc | 18 ++++++++++ > 13 files changed, 202 insertions(+), 12 deletions(-) > create mode 100644 > libstdc++-v3/testsuite/20_util/copyable_function/incomplete_neg.cc > create mode 100644 > libstdc++-v3/testsuite/20_util/function_ref/incomplete_neg.cc > create mode 100644 > libstdc++-v3/testsuite/20_util/move_only_function/incomplete_neg.cc > > diff --git a/libstdc++-v3/include/bits/cpyfunc_impl.h > b/libstdc++-v3/include/bits/cpyfunc_impl.h > index bc44cd3e313..f1918ddf87a 100644 > --- a/libstdc++-v3/include/bits/cpyfunc_impl.h > +++ b/libstdc++-v3/include/bits/cpyfunc_impl.h > @@ -64,6 +64,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > _GLIBCXX_MOF_REF noexcept(_Noex)> > : __polyfunc::_Cpy_base > { > + static_assert( > + (std::__is_complete_or_unbounded(__type_identity<_ArgTypes>()) && > ...), > + "each parameter type must be a complete class"); > + > using _Base = __polyfunc::_Cpy_base; > using _Invoker = __polyfunc::_Invoker<_Noex, _Res, _ArgTypes...>; > using _Signature = _Invoker::_Signature; > diff --git a/libstdc++-v3/include/bits/funcref_impl.h > b/libstdc++-v3/include/bits/funcref_impl.h > index 1e19866035f..44c992281be 100644 > --- a/libstdc++-v3/include/bits/funcref_impl.h > +++ b/libstdc++-v3/include/bits/funcref_impl.h > @@ -68,6 +68,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > class function_ref<_Res(_ArgTypes...) _GLIBCXX_MOF_CV > noexcept(_Noex)> > { > + static_assert( > + (std::__is_complete_or_unbounded(__type_identity<_ArgTypes>()) && > ...), > + "each parameter type must be a complete class"); > + > using _Invoker = __polyfunc::_Invoker<_Noex, _Res, _ArgTypes...>; > using _Signature = _Invoker::_Signature; > > diff --git a/libstdc++-v3/include/bits/funcwrap.h > b/libstdc++-v3/include/bits/funcwrap.h > index cf261bcd4c8..9db4ab7811a 100644 > --- a/libstdc++-v3/include/bits/funcwrap.h > +++ b/libstdc++-v3/include/bits/funcwrap.h > @@ -199,7 +199,25 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > }; > > template<typename _Tp> > - using __param_t = __conditional_t<is_scalar_v<_Tp>, _Tp, _Tp&&>; > + consteval bool > + __pass_by_value() > + { > + // n.b. sizeof(Incomplete&) is ill-formed for incomplete types, > + // so we check is_reference_v first. > + if constexpr (is_reference_v<_Tp> || is_scalar_v<_Tp>) > + return true; > + else > + // n.b. we already asserted that types are complete in wrappers, > + // avoid triggering additional errors from this function. > + if constexpr > (std::__is_complete_or_unbounded(__type_identity<_Tp>())) > + if constexpr (sizeof(_Tp) <= 2 * sizeof(void*)) > + return is_trivially_move_constructible_v<_Tp> > + && is_trivially_destructible_v<_Tp>; > + return false; > + } > + > + template<typename _Tp> > + using __param_t = __conditional_t<__pass_by_value<_Tp>(), _Tp, _Tp&&>; > > template<bool _Noex, typename _Ret, typename... _Args> > using _Invoker = _Base_invoker<_Noex, remove_cv_t<_Ret>, > __param_t<_Args>...>; > diff --git a/libstdc++-v3/include/bits/mofunc_impl.h > b/libstdc++-v3/include/bits/mofunc_impl.h > index 1ceb910e815..468e6855fc6 100644 > --- a/libstdc++-v3/include/bits/mofunc_impl.h > +++ b/libstdc++-v3/include/bits/mofunc_impl.h > @@ -64,6 +64,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > _GLIBCXX_MOF_REF noexcept(_Noex)> > : __polyfunc::_Mo_base > { > + static_assert( > + (std::__is_complete_or_unbounded(__type_identity<_ArgTypes>()) && > ...), > + "each parameter type must be a complete class"); > + > using _Base = __polyfunc::_Mo_base; > using _Invoker = __polyfunc::_Invoker<_Noex, _Res, _ArgTypes...>; > using _Signature = _Invoker::_Signature; > diff --git a/libstdc++-v3/testsuite/20_util/copyable_function/call.cc > b/libstdc++-v3/testsuite/20_util/copyable_function/call.cc > index cf997577f62..0ac5348f2fe 100644 > --- a/libstdc++-v3/testsuite/20_util/copyable_function/call.cc > +++ b/libstdc++-v3/testsuite/20_util/copyable_function/call.cc > @@ -204,13 +204,14 @@ test05() > } > > struct Incomplete; > +enum CompleteEnum : int; > > void > test_params() > { > - std::copyable_function<void(Incomplete)> f1; > - std::copyable_function<void(Incomplete&)> f2; > - std::copyable_function<void(Incomplete&&)> f3; > + std::copyable_function<void(Incomplete&)> f1; > + std::copyable_function<void(Incomplete&&)> f2; > + std::copyable_function<void(CompleteEnum)> f4; > } > > int main() > diff --git a/libstdc++-v3/testsuite/20_util/copyable_function/conv.cc > b/libstdc++-v3/testsuite/20_util/copyable_function/conv.cc > index e678e166172..11c839b6932 100644 > --- a/libstdc++-v3/testsuite/20_util/copyable_function/conv.cc > +++ b/libstdc++-v3/testsuite/20_util/copyable_function/conv.cc > @@ -2,6 +2,7 @@ > // { dg-require-effective-target hosted } > > #include <functional> > +#include <string_view> > #include <testsuite_hooks.h> > > using std::copyable_function; > @@ -15,6 +16,21 @@ static_assert( > !std::is_constructible_v<std::copyable_function<void()&>, > static_assert( !std::is_constructible_v<std::copyable_function<void() const>, > std::copyable_function<void()>> ); > > +using FuncType = int(int); > + > +// Top level const qualifiers are ignored and decay is performed in > parameters > +// of function_types. > +static_assert( std::is_same_v<std::copyable_function<void(int const)>, > + std::copyable_function<void(int)>> ); > +static_assert( std::is_same_v<std::copyable_function<void(int[2])>, > + std::copyable_function<void(int*)>>); > +static_assert( std::is_same_v<std::copyable_function<void(int[])>, > + std::copyable_function<void(int*)>>); > +static_assert( std::is_same_v<std::copyable_function<void(int const[5])>, > + std::copyable_function<void(int const*)>>); > +static_assert( std::is_same_v<std::copyable_function<void(FuncType)>, > + std::copyable_function<void(FuncType*)>>); > + > // Non-trivial args, guarantess that type is not passed by copy > struct CountedArg > { > @@ -240,6 +256,24 @@ test06() > VERIFY( f2(c) == 2 ); > } > > +void > +test07() > +{ > + // Scalar types and small trivially move constructible types are passed > + // by value to invoker. So int&& signature is not compatible for such > types. > + auto fi = [](CountedArg const& arg, int) noexcept { return arg.counter; }; > + std::copyable_function<int(CountedArg, int) const noexcept> ci1(fi); > + VERIFY( ci1(c, 0) == 1 ); > + std::copyable_function<int(CountedArg, int&&) const noexcept> ci2(ci1); > + VERIFY( ci2(c, 0) == 2 ); > + > + auto fs = [](CountedArg const& arg, std::string_view) noexcept { return > arg.counter; }; > + std::copyable_function<int(CountedArg, std::string_view) const noexcept> > cs1(fs); > + VERIFY( cs1(c, "") == 1 ); > + std::copyable_function<int(CountedArg, std::string_view&&) const noexcept> > cs2(cs1); > + VERIFY( cs2(c, "") == 2 ); > +} > + > int main() > { > test01(); > @@ -248,4 +282,5 @@ int main() > test04(); > test05(); > test06(); > + test07(); > } > diff --git > a/libstdc++-v3/testsuite/20_util/copyable_function/incomplete_neg.cc > b/libstdc++-v3/testsuite/20_util/copyable_function/incomplete_neg.cc > new file mode 100644 > index 00000000000..21ddde0d2a1 > --- /dev/null > +++ b/libstdc++-v3/testsuite/20_util/copyable_function/incomplete_neg.cc > @@ -0,0 +1,18 @@ > +// { dg-do compile { target c++26 } } > + > +#include <functional> > + > +struct IncompleteClass; > + > +using T1 = std::copyable_function<int(IncompleteClass)>::result_type; // { > dg-error "here" } > +using T2 = std::copyable_function<int(int, IncompleteClass)>::result_type; > // { dg-error "here" } > + > +enum Enum { > + x = [] { > + // Enum enumeration is incomplete here > + using T3 = std::copyable_function<int(Enum)>::result_type; // { dg-error > "here" } > + return T3(1); > + }() > +}; > + > +// { dg-error "static assertion failed" "" { target *-*-* } 0 } > diff --git a/libstdc++-v3/testsuite/20_util/function_ref/call.cc > b/libstdc++-v3/testsuite/20_util/function_ref/call.cc > index a91c6b4abb9..23253c33ee3 100644 > --- a/libstdc++-v3/testsuite/20_util/function_ref/call.cc > +++ b/libstdc++-v3/testsuite/20_util/function_ref/call.cc > @@ -164,16 +164,16 @@ test05() > } > > struct Incomplete; > +enum CompleteEnum : int; > > void > test_params() > { > auto f = [](auto&&) {}; > - // There is discussion if this is supported. > - // std::function_ref<void(Incomplete)> f1(f); > - std::function_ref<void(Incomplete&)> f2(f); > - // See PR120259, this should be supported. > - // std::function_ref<void(Incomplete&&)> f3(f); > + std::function_ref<void(Incomplete&)> f1(f); > + // See PR libstdc++/120259, this should be supported. > + // std::function_ref<void(Incomplete&&)> f2(f); > + std::function_ref<void(CompleteEnum)> f3(f); > } > > int main() > diff --git a/libstdc++-v3/testsuite/20_util/function_ref/conv.cc > b/libstdc++-v3/testsuite/20_util/function_ref/conv.cc > index 7dc7b8ceac0..7606d265f98 100644 > --- a/libstdc++-v3/testsuite/20_util/function_ref/conv.cc > +++ b/libstdc++-v3/testsuite/20_util/function_ref/conv.cc > @@ -2,6 +2,7 @@ > // { dg-require-effective-target hosted } > > #include <functional> > +#include <string_view> > #include <testsuite_hooks.h> > > using std::function_ref; > @@ -20,6 +21,21 @@ struct CountedArg > }; > CountedArg const c; > > +using FuncType = int(int); > + > +// Top level const qualifiers are ignored in function types, and decay > +// is performed. > +static_assert( std::is_same_v<std::function_ref<void(int const)>, > + std::function_ref<void(int)>> ); > +static_assert( std::is_same_v<std::function_ref<void(int[2])>, > + std::function_ref<void(int*)>>); > +static_assert( std::is_same_v<std::function_ref<void(int[])>, > + std::function_ref<void(int*)>>); > +static_assert( std::is_same_v<std::function_ref<void(int const[5])>, > + std::function_ref<void(int const*)>>); > +static_assert( std::is_same_v<std::function_ref<void(FuncType)>, > + std::function_ref<void(FuncType*)>>); > + > // The C++26 [func.wrap.general] p2 does not currently cover funciton_ref, > // so we make extra copies of arguments. > > @@ -244,6 +260,23 @@ test08() > return true; > }; > > +void > +test09() > +{ > + // Scalar types and small trivially move constructible types are passed > + // by value to invoker. So int&& signature is not compatible for such > types. > + auto fi = [](CountedArg const& arg, int) noexcept { return arg.counter; }; > + std::function_ref<int(CountedArg, int) const noexcept> ri1(fi); > + VERIFY( ri1(c, 0) == 1 ); > + std::function_ref<int(CountedArg, int&&) const noexcept> ri2(ri1); > + VERIFY( ri2(c, 0) == 2 ); > + > + auto fs = [](CountedArg const& arg, std::string_view) noexcept { return > arg.counter; }; > + std::function_ref<int(CountedArg, std::string_view) const noexcept> > rs1(fs); > + VERIFY( rs1(c, "") == 1 ); > + std::function_ref<int(CountedArg, std::string_view&&) const noexcept> > rs2(rs1); > + VERIFY( rs2(c, "") == 2 ); > +} > > int main() > { > @@ -254,6 +287,7 @@ int main() > test05(); > test06(); > test07(); > + test09(); > > static_assert( test08() ); > } > diff --git a/libstdc++-v3/testsuite/20_util/function_ref/incomplete_neg.cc > b/libstdc++-v3/testsuite/20_util/function_ref/incomplete_neg.cc > new file mode 100644 > index 00000000000..c8db1eee42c > --- /dev/null > +++ b/libstdc++-v3/testsuite/20_util/function_ref/incomplete_neg.cc > @@ -0,0 +1,18 @@ > +// { dg-do compile { target c++26 } } > + > +#include <functional> > + > +struct IncompleteClass; > + > +int a1 = alignof(std::function_ref<int(IncompleteClass)>); // { dg-error > "here" } > +int a2 = alignof(std::function_ref<int(int, IncompleteClass)>); // { > dg-error "here" } > + > +enum Enum { > + x = [] { > + // Enum enumeration is incomplete here > + int a3 = alignof(std::function_ref<int(Enum)>); // { dg-error "here" } > + return 1; > + }() > +}; > + > +// { dg-error "static assertion failed" "" { target *-*-* } 0 } > diff --git a/libstdc++-v3/testsuite/20_util/move_only_function/call.cc > b/libstdc++-v3/testsuite/20_util/move_only_function/call.cc > index 217de374763..72c8118d716 100644 > --- a/libstdc++-v3/testsuite/20_util/move_only_function/call.cc > +++ b/libstdc++-v3/testsuite/20_util/move_only_function/call.cc > @@ -204,13 +204,14 @@ test05() > } > > struct Incomplete; > +enum CompleteEnum : int; > > void > test_params() > { > - std::move_only_function<void(Incomplete)> f1; > - std::move_only_function<void(Incomplete&)> f2; > - std::move_only_function<void(Incomplete&&)> f3; > + std::move_only_function<void(Incomplete&)> f1; > + std::move_only_function<void(Incomplete&&)> f2; > + std::move_only_function<void(CompleteEnum)> f4; > } > > int main() > diff --git a/libstdc++-v3/testsuite/20_util/move_only_function/conv.cc > b/libstdc++-v3/testsuite/20_util/move_only_function/conv.cc > index 3da5e9e90a3..ef8bb37b232 100644 > --- a/libstdc++-v3/testsuite/20_util/move_only_function/conv.cc > +++ b/libstdc++-v3/testsuite/20_util/move_only_function/conv.cc > @@ -2,6 +2,7 @@ > // { dg-require-effective-target hosted } > > #include <functional> > +#include <string_view> > #include <testsuite_hooks.h> > > using std::move_only_function; > @@ -15,6 +16,21 @@ static_assert( > !std::is_constructible_v<std::move_only_function<void()&>, > static_assert( !std::is_constructible_v<std::move_only_function<void() > const>, > std::move_only_function<void()>> ); > > +using FuncType = int(int); > + > +// Top level const qualifiers are ignored in function types, and decay > +// is performed. > +static_assert( std::is_same_v<std::move_only_function<void(int const)>, > + std::move_only_function<void(int)>> ); > +static_assert( std::is_same_v<std::move_only_function<void(int[2])>, > + std::move_only_function<void(int*)>>); > +static_assert( std::is_same_v<std::move_only_function<void(int[])>, > + std::move_only_function<void(int*)>>); > +static_assert( std::is_same_v<std::move_only_function<void(int const[5])>, > + std::move_only_function<void(int const*)>>); > +static_assert( std::is_same_v<std::move_only_function<void(FuncType)>, > + std::move_only_function<void(FuncType*)>>); > + > // Non-trivial args, guarantess that type is not passed by copy > struct CountedArg > { > @@ -177,6 +193,24 @@ test06() > VERIFY( m1(c) == 2 ); > } > > +void > +test07() > +{ > + // Scalar types and small trivially move constructible types are passed > + // by value to invoker. So int&& signature is not compatible for such > types. > + auto fi = [](CountedArg const& arg, int) noexcept { return arg.counter; }; > + std::move_only_function<int(CountedArg, int) const noexcept> mi1(fi); > + VERIFY( mi1(c, 0) == 1 ); > + std::move_only_function<int(CountedArg, int&&) const noexcept> > mi2(std::move(mi1)); > + VERIFY( mi2(c, 0) == 2 ); > + > + auto fs = [](CountedArg const& arg, std::string_view) noexcept { return > arg.counter; }; > + std::move_only_function<int(CountedArg, std::string_view) const noexcept> > ms1(fs); > + VERIFY( ms1(c, "") == 1 ); > + std::move_only_function<int(CountedArg, std::string_view&&) const > noexcept> ms2(std::move(ms1)); > + VERIFY( ms2(c, "") == 2 ); > +} > + > int main() > { > test01(); > @@ -185,4 +219,5 @@ int main() > test04(); > test05(); > test06(); > + test07(); > } > diff --git > a/libstdc++-v3/testsuite/20_util/move_only_function/incomplete_neg.cc > b/libstdc++-v3/testsuite/20_util/move_only_function/incomplete_neg.cc > new file mode 100644 > index 00000000000..d025c473e28 > --- /dev/null > +++ b/libstdc++-v3/testsuite/20_util/move_only_function/incomplete_neg.cc > @@ -0,0 +1,18 @@ > +// { dg-do compile { target c++23 } } > + > +#include <functional> > + > +struct IncompleteClass; > + > +using T1 = std::move_only_function<int(IncompleteClass)>::result_type; // { > dg-error "here" } > +using T2 = std::move_only_function<int(int, IncompleteClass)>::result_type; > // { dg-error "here" } > + > +enum Enum { > + x = [] { > + // Enum enumeration is incomplete here > + using T3 = std::move_only_function<int(Enum)>::result_type; // { > dg-error "here" } > + return T3(1); > + }() > +}; > + > +// { dg-error "static assertion failed" "" { target *-*-* } 0 } > -- > 2.49.0 >