On Wed, 11 Feb 2026 at 12:19, Tomasz Kamiński <[email protected]> wrote:
>
> This patch makes the function_ref non-dangling for the stateless
> wrappers:
> * any functor for which operator() selected for arguments
> is static,
> * standard functors, including pre-C++20 ones.
> In other words, for function_ref fr is constructed from stateless wrapper
> w, can be still called after the object w is destroyed, e.g.:
> std::function_ref<bool(int, int)> fr(std::ranges::less{});
> fr(1, 2); // OK, previously UB because fr reffered to already destroyed
> // temporary
> As function_ref's operator() is not contexpr, we test the change by checking
> if the above declaration made be made constexpr, as such variable cannot
> contain
> dangling poitner values.
>
> We adjust the function_ref generic constructor from any functor, to adjust
> using more specialized invoker:
> * _S_static (newly added) if the called operator() overload is static,
> after changes r16-5624-g0ea9d760fbf44c, includes all post-c++20 functors
> * _S_nttp<_Fd{}> for pre-C++20 standard functors
> In both above cases the value of _M_ptrs is ignored and simply set to nullptr.
>
> This follows same technique (checking _Fd::operator()(args...)), and support
> the same set of types, as for one used for the transform views iterators in
> r16-5625-g9ed821d107f7a1.
>
> As after this change we provide well-defined behavior for the code, that
> previous was undefined, this changes is pure quality-of-implementation.
> As illustrated by the test cases, it has observable side effects, where
> non-longer dangling construct can be used to initialize constexpr
> function_ref. However, the standared does not define when the constructors
> defined constexpr are actually usable at compile time, and the already have
> precendence in form of SSO string for such constructs being implementation
> specific.
OK
>
> libstdc++-v3/ChangeLog:
>
> * include/bits/funcref_impl.h (function_ref::function_ref(_Fn&&)):
> Use _S_static and _S_nttp invokers.
> * include/bits/funcwrap.h (_Base_invoker::_S_static):
> Define.
> * include/bits/stl_function.h (std::__is_std_op_template)
> (std::__is_std_op_wrapper) [__cplusplus > 201703L]:
> Moved from std/ranges.
> * include/std/ranges (__detail::__is_std_op_template):
> (__detail::__is_std_op_wrapper): Moved to bits/stl_function.h.
> * testsuite/20_util/function_ref/dangling.cc: New test.
> * testsuite/20_util/function_ref/dangling_neg.cc: New test.
> ---
> v2 expand commit description illustrating what the change really
> achieves, and why it is not tested directly.
>
> libstdc++-v3/include/bits/funcref_impl.h | 20 ++++-
> libstdc++-v3/include/bits/funcwrap.h | 8 ++
> libstdc++-v3/include/bits/stl_function.h | 50 ++++++++++++
> libstdc++-v3/include/std/ranges | 50 ------------
> .../20_util/function_ref/dangling.cc | 74 ++++++++++++++++++
> .../20_util/function_ref/dangling_neg.cc | 76 +++++++++++++++++++
> 6 files changed, 226 insertions(+), 52 deletions(-)
> create mode 100644 libstdc++-v3/testsuite/20_util/function_ref/dangling.cc
> create mode 100644
> libstdc++-v3/testsuite/20_util/function_ref/dangling_neg.cc
>
> diff --git a/libstdc++-v3/include/bits/funcref_impl.h
> b/libstdc++-v3/include/bits/funcref_impl.h
> index 3fe2fb1b3f5..3d55c8406b0 100644
> --- a/libstdc++-v3/include/bits/funcref_impl.h
> +++ b/libstdc++-v3/include/bits/funcref_impl.h
> @@ -106,8 +106,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> constexpr
> function_ref(_Fn&& __f) noexcept
> {
> - _M_invoke = _Invoker::template _S_ptrs<_Vt _GLIBCXX_MOF_CV&>();
> - _M_init(std::addressof(__f));
> + using _Fd = remove_cv_t<_Vt>;
> + if constexpr (__is_std_op_wrapper<_Fd>)
> + {
> + _M_invoke = _Invoker::template _S_nttp<_Fd{}>;
> + _M_ptrs._M_obj = nullptr;
> + }
> + else if constexpr (requires (_ArgTypes&&... __args) {
> + _Fd::operator()(std::forward<_ArgTypes>(__args)...);
> + })
> + {
> + _M_invoke = _Invoker::template _S_static<_Fd>;
> + _M_ptrs._M_obj = nullptr;
> + }
> + else
> + {
> + _M_invoke = _Invoker::template _S_ptrs<_Vt _GLIBCXX_MOF_CV&>();
> + _M_init(std::addressof(__f));
> + }
> }
>
> // _GLIBCXX_RESOLVE_LIB_DEFECTS
> diff --git a/libstdc++-v3/include/bits/funcwrap.h
> b/libstdc++-v3/include/bits/funcwrap.h
> index b8dd6fb7aea..6441893d213 100644
> --- a/libstdc++-v3/include/bits/funcwrap.h
> +++ b/libstdc++-v3/include/bits/funcwrap.h
> @@ -41,6 +41,9 @@
>
> #include <bits/invoke.h>
> #include <bits/utility.h>
> +#if __glibcxx_function_ref
> +#include <bits/stl_function.h>
> +#endif
>
> namespace std _GLIBCXX_VISIBILITY(default)
> {
> @@ -139,6 +142,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> { return &_S_call_ptrs<_Adjust_target<_Tp>>; }
>
> #ifdef __glibcxx_function_ref // C++ >= 26
> + template<typename _Fn>
> + static _Ret
> + _S_static(_Ptrs, _Args... __args) noexcept(_Noex)
> + { return _Fn::operator()(std::forward<_Args>(__args)...); }
> +
> template<auto __fn>
> static _Ret
> _S_nttp(_Ptrs, _Args... __args) noexcept(_Noex)
> diff --git a/libstdc++-v3/include/bits/stl_function.h
> b/libstdc++-v3/include/bits/stl_function.h
> index 0600de72b10..6609fe3199b 100644
> --- a/libstdc++-v3/include/bits/stl_function.h
> +++ b/libstdc++-v3/include/bits/stl_function.h
> @@ -1525,6 +1525,56 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> #endif
> #endif
>
> +#if __cplusplus > 201703L
> + template<template<typename> class>
> + constexpr bool __is_std_op_template = false;
> +
> + template<>
> + inline constexpr bool __is_std_op_template<std::equal_to> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::not_equal_to> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::greater> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::less> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::greater_equal> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::less_equal> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::plus> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::minus> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::multiplies> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::divides> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::modulus> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::negate> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::logical_and> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::logical_or> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::logical_not> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::bit_and> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::bit_or> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::bit_xor> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::bit_not> = true;
> +
> + template<typename _Fn>
> + constexpr bool __is_std_op_wrapper = false;
> +
> + template<template<typename> class _Ft, typename _Tp>
> + constexpr bool __is_std_op_wrapper<_Ft<_Tp>>
> + = __is_std_op_template<_Ft>;
> +#endif
> _GLIBCXX_END_NAMESPACE_VERSION
> } // namespace
>
> diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
> index 430f4108204..8365bed17a6 100644
> --- a/libstdc++-v3/include/std/ranges
> +++ b/libstdc++-v3/include/std/ranges
> @@ -287,56 +287,6 @@ namespace ranges
> { return std::__addressof(_M_value); }
> };
>
> - template<template<typename> class>
> - constexpr bool __is_std_op_template = false;
> -
> - template<>
> - inline constexpr bool __is_std_op_template<std::equal_to> = true;
> - template<>
> - inline constexpr bool __is_std_op_template<std::not_equal_to> = true;
> - template<>
> - inline constexpr bool __is_std_op_template<std::greater> = true;
> - template<>
> - inline constexpr bool __is_std_op_template<std::less> = true;
> - template<>
> - inline constexpr bool __is_std_op_template<std::greater_equal> = true;
> - template<>
> - inline constexpr bool __is_std_op_template<std::less_equal> = true;
> - template<>
> - inline constexpr bool __is_std_op_template<std::plus> = true;
> - template<>
> - inline constexpr bool __is_std_op_template<std::minus> = true;
> - template<>
> - inline constexpr bool __is_std_op_template<std::multiplies> = true;
> - template<>
> - inline constexpr bool __is_std_op_template<std::divides> = true;
> - template<>
> - inline constexpr bool __is_std_op_template<std::modulus> = true;
> - template<>
> - inline constexpr bool __is_std_op_template<std::negate> = true;
> - template<>
> - inline constexpr bool __is_std_op_template<std::logical_and> = true;
> - template<>
> - inline constexpr bool __is_std_op_template<std::logical_or> = true;
> - template<>
> - inline constexpr bool __is_std_op_template<std::logical_not> = true;
> - template<>
> - inline constexpr bool __is_std_op_template<std::bit_and> = true;
> - template<>
> - inline constexpr bool __is_std_op_template<std::bit_or> = true;
> - template<>
> - inline constexpr bool __is_std_op_template<std::bit_xor> = true;
> - template<>
> - inline constexpr bool __is_std_op_template<std::bit_not> = true;
> -
> -
> - template<typename _Fn>
> - constexpr bool __is_std_op_wrapper = false;
> -
> - template<template<typename> class _Ft, typename _Tp>
> - constexpr bool __is_std_op_wrapper<_Ft<_Tp>>
> - = __is_std_op_template<_Ft>;
> -
> namespace __func_handle
> {
> template<typename _Fn>
> diff --git a/libstdc++-v3/testsuite/20_util/function_ref/dangling.cc
> b/libstdc++-v3/testsuite/20_util/function_ref/dangling.cc
> new file mode 100644
> index 00000000000..3cc782524f6
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/function_ref/dangling.cc
> @@ -0,0 +1,74 @@
> +// { dg-do compile { target c++26 } }
> +
> +#include <functional>
> +
> +template<typename F>
> +constexpr std::function_ref<int(int, int) const>
> +create(F f)
> +{
> + std::function_ref<int(int, int) const> fr(f);
> + return fr;
> +}
> +
> +constexpr auto vLambda = create([](int x, int y) static { return x + y; });
> +
> +constexpr auto vTgreater = create(std::greater<int>{});
> +constexpr auto vDgreater = create(std::greater<>{});
> +constexpr auto vRgreater = create(std::ranges::greater{});
> +
> +constexpr auto vTplus = create(std::plus<int>{});
> +constexpr auto vDplus = create(std::plus<>{});
> +
> +struct Empty
> +{
> + static int
> + operator()(int x, int y)
> + { return x + y; }
> +};
> +
> +constexpr auto vEmpty = create(Empty{});
> +
> +struct NonEmpty {
> + int v;
> +
> + static int
> + operator()(int x, int y)
> + { return x + y; }
> +};
> +
> +constexpr auto vNonEmpty = create(NonEmpty{3});
> +
> +struct NonStatic {
> + int v;
> +
> + int
> + operator()(int x, int y) const
> + { return x + y + v; }
> +};
> +
> +constexpr auto vNonType = create(std::nontype<NonStatic{3}>);
> +
> +struct StaticWins {
> + static int
> + operator()(int x, int y)
> + { return x + y; }
> +
> + int
> + operator()(float x, float y) const
> + { return x + y; }
> +};
> +
> +constexpr auto vStaticWins = create(StaticWins{});
> +
> +struct StaticWinsET {
> + static int
> + operator()(int x, int y)
> + { return x + y; }
> +
> + int
> + operator()(this StaticWinsET, float x, float y)
> + { return x + y; }
> +};
> +
> +constexpr auto vStaticWinsET = create(StaticWinsET{});
> +
> diff --git a/libstdc++-v3/testsuite/20_util/function_ref/dangling_neg.cc
> b/libstdc++-v3/testsuite/20_util/function_ref/dangling_neg.cc
> new file mode 100644
> index 00000000000..16033014703
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/function_ref/dangling_neg.cc
> @@ -0,0 +1,76 @@
> +// { dg-do compile { target c++26 } }
> +
> +#include <functional>
> +
> +template<typename F>
> +constexpr std::function_ref<int(int, int) const>
> +create(F f)
> +{
> + std::function_ref<int(int, int) const> fr(f);
> + return fr;
> +}
> +
> +constexpr auto vLambda = create([](int x, int y) { return x + y; }); // {
> dg-error "is not a constant expression" }
> +
> +struct Empty
> +{
> + int
> + operator()(int x, int y) const
> + { return x + y; }
> +};
> +
> +constexpr auto vEmpty = create(Empty{}); // { dg-error "is not a constant
> expression" }
> +
> +struct NonEmpty {
> + int v;
> +
> + int
> + operator()(int x, int y) const
> + { return x + y + v; }
> +};
> +
> +constexpr auto vNonEmpty = create(NonEmpty{3}); // { dg-error "is not a
> constant expression" }
> +
> +struct InstanceWins {
> + int
> + operator()(int x, int y) const
> + { return x + y; }
> +
> + static int
> + operator()(float x, float y)
> + { return x + y; }
> +};
> +
> +constexpr auto vInstanceWins = create(InstanceWins{}); // { dg-error "is not
> a constant expression" }
> +
> +struct EmptyET
> +{
> + int
> + operator()(this EmptyET, int x, int y)
> + { return x + y; }
> +};
> +
> +constexpr auto vEmptyET = create(EmptyET{}); // { dg-error "is not a
> constant expression" }
> +
> +struct NonEmptyET {
> + int v;
> +
> + int
> + operator()(this NonEmptyET s, int x, int y)
> + { return x + y + s.v; }
> +};
> +
> +constexpr auto vNonEmptyET = create(NonEmptyET{3}); // { dg-error "is not a
> constant expression" }
> +
> +struct InstanceWinsET {
> + int
> + operator()(this InstanceWinsET, int x, int y)
> + { return x + y; }
> +
> + static int
> + operator()(float x, float y)
> + { return x + y; }
> +};
> +
> +constexpr auto vInstanceWinsET = create(InstanceWinsET{}); // { dg-error "is
> not a constant expression" }
> +
> --
> 2.53.0
>