[PATCH] D123576: Support constructing empty function_ref from other callables that can be "empty"

Mehdi AMINI via Phabricator via llvm-commits llvm-commits at lists.llvm.org
Tue Apr 12 01:10:12 PDT 2022


mehdi_amini created this revision.
mehdi_amini added reviewers: csigg, dexonsmith.
Herald added a project: All.
mehdi_amini requested review of this revision.
Herald added a project: LLVM.
Herald added a subscriber: llvm-commits.

This fixes a surprising behavior where changing an existing function_ref
to std::function can break some code in a subtle manner. Here is the
pattern:

  void foo(llvm::function_ref<void()) callback) {
    if (callback) callback();
  }
  
  void bar(llvm::function_ref<void()) callback) {
    foo(callback);
    // do something else with callback, accept an empty callback.
  }

Assuming `bar` needs to change to take a std::function (for example
to be able to store it), the code in foo() would crash if `callback`
is a default constructed function.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D123576

Files:
  llvm/include/llvm/ADT/STLFunctionalExtras.h
  llvm/unittests/ADT/FunctionRefTest.cpp


Index: llvm/unittests/ADT/FunctionRefTest.cpp
===================================================================
--- llvm/unittests/ADT/FunctionRefTest.cpp
+++ llvm/unittests/ADT/FunctionRefTest.cpp
@@ -59,4 +59,11 @@
   EXPECT_EQ("string", returns([] { return "hello"; }));
 }
 
+TEST(FunctionRefTest, std_function) {
+  // Check that function_ref constructed from an empty std::function is empty.
+  std::function<void()> f;
+  function_ref<void()> f_ref(f);
+  ASSERT_FALSE((bool)f_ref);
+}
+
 } // namespace
Index: llvm/include/llvm/ADT/STLFunctionalExtras.h
===================================================================
--- llvm/include/llvm/ADT/STLFunctionalExtras.h
+++ llvm/include/llvm/ADT/STLFunctionalExtras.h
@@ -56,6 +56,8 @@
       // This is not the copy-constructor.
       std::enable_if_t<!std::is_same<remove_cvref_t<Callable>,
                                      function_ref>::value> * = nullptr,
+      // callable is not convertible to bool
+      std::enable_if_t<!std::is_convertible<Callable, bool>::value> * = nullptr,
       // Functor must be callable and return a suitable type.
       std::enable_if_t<std::is_void<Ret>::value ||
                        std::is_convertible<decltype(std::declval<Callable>()(
@@ -64,6 +66,28 @@
       : callback(callback_fn<typename std::remove_reference<Callable>::type>),
         callable(reinterpret_cast<intptr_t>(&callable)) {}
 
+  template <typename Callable>
+  function_ref(
+      Callable &&callable,
+      // This is not the copy-constructor.
+      std::enable_if_t<!std::is_same<remove_cvref_t<Callable>,
+                                     function_ref>::value> * = nullptr,
+      // callable is convertible to bool
+      std::enable_if_t<std::is_convertible<Callable, bool>::value> * = nullptr,
+      // Functor must be callable and return a suitable type.
+      std::enable_if_t<std::is_void<Ret>::value ||
+                       std::is_convertible<decltype(std::declval<Callable>()(
+                                               std::declval<Params>()...)),
+                                           Ret>::value> * = nullptr)
+      : callback(callback_fn<typename std::remove_reference<Callable>::type>),
+        callable(reinterpret_cast<intptr_t>(&callable)) {
+    // Handles construction from an empty callable (for example a default
+    // constructed std::function) and ensure that bool conversion to `false` is
+    // propagated.
+    if (!callable)
+      callback = nullptr;
+  }
+
   Ret operator()(Params ...params) const {
     return callback(callable, std::forward<Params>(params)...);
   }


-------------- next part --------------
A non-text attachment was scrubbed...
Name: D123576.422138.patch
Type: text/x-patch
Size: 2615 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/llvm-commits/attachments/20220412/a9038ce7/attachment.bin>


More information about the llvm-commits mailing list