https://gcc.gnu.org/g:b58cf254437aa78046666b7444a23e2658396f80

commit r16-7462-gb58cf254437aa78046666b7444a23e2658396f80
Author: Tomasz Kamiński <[email protected]>
Date:   Mon Feb 2 10:19:51 2026 +0100

    libstdc++: Make function_ref non-dangling for stateless wrappers
    
    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, any function_ref fr, that 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 referred to already destroyed
                // temporary
    As function_ref's operator() is not constexpr, we test the change by 
checking
    if the above declaration can be made constexpr, as such variable cannot 
contain
    dangling pointer values.
    
    We adjust the function_ref generic constructor from any functor, to use more
    specialized invoker:
    * _S_static (newly added) if the called operator() overload is static,
      after changes r16-5624-g0ea9d760fbf44c, this covers 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 constructs can be used to define constexpr function_ref.
    However, the standard does not define when the constructors defined 
constexpr
    are actually usable at compile time, and the already have precedent in form
    of SSO string for validity such constructs being implementation specific.
    
    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.
    
    Reviewed-by: Jonathan Wakely <[email protected]>
    Signed-off-by: Tomasz Kamiński <[email protected]>

Diff:
---
 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 --------------
 .../testsuite/20_util/function_ref/dangling.cc     | 74 +++++++++++++++++++++
 .../testsuite/20_util/function_ref/dangling_neg.cc | 76 ++++++++++++++++++++++
 6 files changed, 226 insertions(+), 52 deletions(-)

diff --git a/libstdc++-v3/include/bits/funcref_impl.h 
b/libstdc++-v3/include/bits/funcref_impl.h
index 3fe2fb1b3f52..3d55c8406b09 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 b8dd6fb7aeae..6441893d2135 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 95c24d44b9ef..6429ab4cc07a 100644
--- a/libstdc++-v3/include/bits/stl_function.h
+++ b/libstdc++-v3/include/bits/stl_function.h
@@ -1537,6 +1537,56 @@ template <typename _Kt, typename _Container>
     __not_container_iterator<_Kt, _Container>;
 #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 430f41082042..8365bed17a6e 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 000000000000..3cc782524f6e
--- /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 000000000000..16033014703e
--- /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" }
+

Reply via email to