[libcxx-commits] [libcxx] [libc++] Fix ill-formed return in function_ref's static-call path (PR #228868)

via libcxx-commits libcxx-commits at lists.llvm.org
Sun Oct 4 02:53:10 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libcxx

Author: Kim-J-Smith

<details>
<summary>Changes</summary>

In `libcxx/include/__functional/function_ref_impl.h`:
The current `__statically_callable` branch is ill-formed when the lambda's return type `_Rp` is void but the target's static operator() returns non-void. This patch discards the return value when `_Rp` is void to make it behave the same as `invoke_r<_Rp>`.

Fixes errors like this:
```cpp
#include <functional>

struct F { static int operator()() { return 42; } };

int main() {
    std::invoke_r<void>(F{}); // OK
    std::function_ref<void()> f = F{}; // error: void lambda should not return a value
}
```

Also adds test coverage binding a non-void static `operator()` to `function_ref<void(...)>` in all four cv/noexcept variants.

Fixes #<!-- -->228717

---
Full diff: https://github.com/llvm/llvm-project/pull/228868.diff


2 Files Affected:

- (modified) libcxx/include/__functional/function_ref_impl.h (+4-1) 
- (modified) libcxx/test/std/utilities/function.objects/func.wrap/func.wrap.ref/func.wrap.ref.inv/invoke.pass.cpp (+37) 


``````````diff
diff --git a/libcxx/include/__functional/function_ref_impl.h b/libcxx/include/__functional/function_ref_impl.h
index ada14643ec496..b54d359853e9b 100644
--- a/libcxx/include/__functional/function_ref_impl.h
+++ b/libcxx/include/__functional/function_ref_impl.h
@@ -106,7 +106,10 @@ class function_ref<_Rp(_ArgTypes...) _LIBCPP_FUNCTION_REF_CV noexcept(__is_noexc
     using _Dn = remove_cv_t<_Tp>;
     if constexpr (__statically_callable<_Dn, __arg_t<_ArgTypes>...>) {
       __call_ = [](__storage_t, __arg_t<_ArgTypes>... __args) static noexcept(__is_noexcept) -> _Rp {
-        return _Dn::operator()(std::forward<__arg_t<_ArgTypes>>(__args)...);
+        if constexpr (is_void_v<_Rp>)
+          _Dn::operator()(std::forward<__arg_t<_ArgTypes>>(__args)...);
+        else
+          return _Dn::operator()(std::forward<__arg_t<_ArgTypes>>(__args)...);
       };
     } else {
       __storage_ = __storage_t(std::addressof(__obj)),
diff --git a/libcxx/test/std/utilities/function.objects/func.wrap/func.wrap.ref/func.wrap.ref.inv/invoke.pass.cpp b/libcxx/test/std/utilities/function.objects/func.wrap/func.wrap.ref/func.wrap.ref.inv/invoke.pass.cpp
index 820fed5fc76bc..f2aff57497da5 100644
--- a/libcxx/test/std/utilities/function.objects/func.wrap/func.wrap.ref/func.wrap.ref.inv/invoke.pass.cpp
+++ b/libcxx/test/std/utilities/function.objects/func.wrap/func.wrap.ref/func.wrap.ref.inv/invoke.pass.cpp
@@ -93,6 +93,11 @@ struct TrackCopyMove {
   }
 };
 
+struct StaticallyCallable {
+  static inline int static_call_count = 0;
+  static int operator()() noexcept { static_call_count++; return 42; }
+};
+
 void test_default() {
   {
     std::function_ref<void()> f = [] {};
@@ -195,6 +200,14 @@ void test_default() {
     std::function_ref<void(TrackCopyMove)> f = lambda;
     f(TrackCopyMove{});
   }
+  {
+    // Non-void static operator() can bind to function_ref<void()>
+    StaticallyCallable sc;
+    std::function_ref<void()> f = sc;
+    StaticallyCallable::static_call_count = 0;
+    f();
+    assert(StaticallyCallable::static_call_count == 1);
+  }
 }
 
 void test_const() {
@@ -299,6 +312,14 @@ void test_const() {
     std::function_ref<void(TrackCopyMove) const> f = lambda;
     f(TrackCopyMove{});
   }
+  {
+    // Non-void static operator() can bind to function_ref<void()>
+    StaticallyCallable sc;
+    std::function_ref<void() const> f = sc;
+    StaticallyCallable::static_call_count = 0;
+    f();
+    assert(StaticallyCallable::static_call_count == 1);
+  }
 }
 
 void test_noexcept() {
@@ -404,6 +425,14 @@ void test_noexcept() {
     std::function_ref<void(TrackCopyMove) noexcept> f = lambda;
     f(TrackCopyMove{});
   }
+  {
+    // Non-void static operator() can bind to function_ref<void()>
+    StaticallyCallable sc;
+    std::function_ref<void() noexcept> f = sc;
+    StaticallyCallable::static_call_count = 0;
+    f();
+    assert(StaticallyCallable::static_call_count == 1);
+  }
 }
 
 void test_const_noexcept() {
@@ -509,6 +538,14 @@ void test_const_noexcept() {
     std::function_ref<void(TrackCopyMove) const noexcept> f = lambda;
     f(TrackCopyMove{});
   }
+  {
+    // Non-void static operator() can bind to function_ref<void()>
+    StaticallyCallable sc;
+    std::function_ref<void() const noexcept> f = sc;
+    StaticallyCallable::static_call_count = 0;
+    f();
+    assert(StaticallyCallable::static_call_count == 1);
+  }
 }
 
 void test() {

``````````

</details>


https://github.com/llvm/llvm-project/pull/228868


More information about the libcxx-commits mailing list