On Wed, May 28, 2025 at 4:53 PM Patrick Palka <ppa...@redhat.com> wrote:
> On Wed, 28 May 2025, Tomasz Kamiński 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, the are passed by value to the > invoker, > > they > > > when they are small (at most two pointers), trivially move constructible > and > > trivially destructible. The later guarantees that passing them by value > has not > > latter > > > user visible side effects. > > > > In particular, this extents the set of types forwarded by value, that was > > extends > > > 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 consequence implementation imposes requirements on instantiation of > > move_only_function and copyable_function. To avoid producing the errors > from > > the implementation details, and 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. > > --- > > Tested on x86_54-linux. OK for trunk? > > > > libstdc++-v3/include/bits/cpyfunc_impl.h | 4 +++ > > libstdc++-v3/include/bits/funcref_impl.h | 4 +++ > > libstdc++-v3/include/bits/funcwrap.h | 18 +++++++++- > > 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, 200 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;1 > > > > diff --git a/libstdc++-v3/include/bits/funcwrap.h > b/libstdc++-v3/include/bits/funcwrap.h > > index cf261bcd4c8..4fa9e1e84f2 100644 > > --- a/libstdc++-v3/include/bits/funcwrap.h > > +++ b/libstdc++-v3/include/bits/funcwrap.h > > @@ -199,7 +199,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > }; > > > > template<typename _Tp> > > - using __param_t = __conditional_t<is_scalar_v<_Tp>, _Tp, _Tp&&>; > > + consteval bool __pass_by_rref() > > Newline before the function name > > I find it slightly easier to follow instead define the inverse predicate > __pass_by_value, which IIUC would look like > > if constexpr (__is_complete_or_unbounded) > return false; > else > return is_reference_v<_Tp> > || is_scalar_v<_Tp> > || (sizeof(_Tp) <= 2 * sizeof(void*) > && is_trivially_move_constructible_v > && is_trivially_destructible_v) > This will instantiate sizeof() and is_trivially_blah_v for reference and scalars, and I wanted to explicitly avoid doing so. This is especially important for Incomplete&, where sizeof(Incomplete&) is same as sizeof(Incomplete) and ill-formed. I created this exposition function, so I can do constexpr chains that will avoid doing any of the above. > > I don't feel strongly about it though. > > > + { > > + if constexpr (is_reference_v<_Tp> || is_scalar_v<_Tp>) > > + return false; > > + else if constexpr > (!std::__is_complete_or_unbounded(__type_identity<_Tp>())) > > + // N.B. we already asserted that types are complete in wrappers, > > + // avoid triggering additional errors from this function. > > + return true; > > + else if constexpr (sizeof(_Tp) <= 2 * sizeof(void*)) > > + return !is_trivially_move_constructible_v<_Tp> > > + || !is_trivially_destructible_v<_Tp>; > > Just curious, why check move_constructible + destructible instead of > just is_trivially_copy_constructible? > When we pass objects around, we perform moves and not copies. And I can imagine a class, with a trivial move constructor but non-trivial copy constructor or deleted. So, I am checking if ops that are inserted when passing by-value, have no user visible side-effects, trivial for sure do not. And for checking is_destructible, I am not sure if standard requires that is_trivally_constructible, implies that destructors are trivial. > > > + else > > + return true; > > + } > > + > > + template<typename _Tp> > > + using __param_t = __conditional_t<__pass_by_rref<_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 > > > >