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
>

Reply via email to