[libcxx-commits] [libcxx] 87cc95a - [libc++] Implement P2136R3 std::invoke_r

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Mon Feb 13 05:47:11 PST 2023


Author: Louis Dionne
Date: 2023-02-13T08:46:57-05:00
New Revision: 87cc95a904e04d4d62f884c3937316cdf003c37d

URL: https://github.com/llvm/llvm-project/commit/87cc95a904e04d4d62f884c3937316cdf003c37d
DIFF: https://github.com/llvm/llvm-project/commit/87cc95a904e04d4d62f884c3937316cdf003c37d.diff

LOG: [libc++] Implement P2136R3 std::invoke_r

Differential Revision: https://reviews.llvm.org/D143610

Added: 
    libcxx/test/std/utilities/function.objects/func.invoke/invoke_r.pass.cpp
    libcxx/test/std/utilities/function.objects/func.invoke/invoke_r.temporary.verify.cpp

Modified: 
    libcxx/docs/FeatureTestMacroTable.rst
    libcxx/docs/Status/Cxx2bPapers.csv
    libcxx/include/__functional/invoke.h
    libcxx/include/functional
    libcxx/include/version
    libcxx/test/std/language.support/support.limits/support.limits.general/functional.version.compile.pass.cpp
    libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
    libcxx/utils/generate_feature_test_macro_components.py

Removed: 
    libcxx/test/std/utilities/function.objects/func.invoke/invoke_feature_test_macro.pass.cpp


################################################################################
diff  --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index df4dfc4a9a12a..d070e209d2acd 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -322,7 +322,7 @@ Status
     ------------------------------------------------- -----------------
     ``__cpp_lib_forward_like``                        ``202207L``
     ------------------------------------------------- -----------------
-    ``__cpp_lib_invoke_r``                            *unimplemented*
+    ``__cpp_lib_invoke_r``                            ``202106L``
     ------------------------------------------------- -----------------
     ``__cpp_lib_is_scoped_enum``                      ``202011L``
     ------------------------------------------------- -----------------

diff  --git a/libcxx/docs/Status/Cxx2bPapers.csv b/libcxx/docs/Status/Cxx2bPapers.csv
index 626f6cd9bb107..fbde3d7034ddd 100644
--- a/libcxx/docs/Status/Cxx2bPapers.csv
+++ b/libcxx/docs/Status/Cxx2bPapers.csv
@@ -20,7 +20,7 @@
 "`P1659R3 <https://wg21.link/P1659R3>`__","LWG","starts_with and ends_with","June 2021","","","|ranges|"
 "`P1951R1 <https://wg21.link/P1951R1>`__","LWG","Default Arguments for pair Forwarding Constructor","June 2021","|Complete|","14.0"
 "`P1989R2 <https://wg21.link/P1989R2>`__","LWG","Range constructor for std::string_view","June 2021","|Complete|","14.0","|ranges|"
-"`P2136R3 <https://wg21.link/P2136R3>`__","LWG","invoke_r","June 2021","",""
+"`P2136R3 <https://wg21.link/P2136R3>`__","LWG","invoke_r","June 2021","|Complete|","17.0"
 "`P2166R1 <https://wg21.link/P2166R1>`__","LWG","A Proposal to Prohibit std::basic_string and std::basic_string_view construction from nullptr","June 2021","|Complete|","13.0"
 "","","","","","",""
 "`P0288R9 <https://wg21.link/P0288R9>`__","LWG","``any_invocable``","October 2021","",""

diff  --git a/libcxx/include/__functional/invoke.h b/libcxx/include/__functional/invoke.h
index 48e6eac3ce98f..e5d03750812b2 100644
--- a/libcxx/include/__functional/invoke.h
+++ b/libcxx/include/__functional/invoke.h
@@ -541,6 +541,25 @@ invoke(_Fn&& __f, _Args&&... __args)
 
 #endif // _LIBCPP_STD_VER > 14
 
+#if _LIBCPP_STD_VER >= 23
+template <class _Result, class _Fn, class... _Args>
+  requires is_invocable_r_v<_Result, _Fn, _Args...>
+_LIBCPP_HIDE_FROM_ABI constexpr _Result
+invoke_r(_Fn&& __f, _Args&&... __args) noexcept(is_nothrow_invocable_r_v<_Result, _Fn, _Args...>) {
+    if constexpr (is_void_v<_Result>) {
+        static_cast<void>(std::invoke(std::forward<_Fn>(__f), std::forward<_Args>(__args)...));
+    } else {
+        // TODO: Use reference_converts_from_temporary_v once implemented
+        // using _ImplicitInvokeResult = invoke_result_t<_Fn, _Args...>;
+        // static_assert(!reference_converts_from_temporary_v<_Result, _ImplicitInvokeResult>,
+        static_assert(true,
+            "Returning from invoke_r would bind a temporary object to the reference return type, "
+            "which would result in a dangling reference.");
+        return std::invoke(std::forward<_Fn>(__f), std::forward<_Args>(__args)...);
+    }
+}
+#endif
+
 _LIBCPP_END_NAMESPACE_STD
 
 #endif // _LIBCPP___FUNCTIONAL_INVOKE_H

diff  --git a/libcxx/include/functional b/libcxx/include/functional
index 4dce03429bb1c..26e4ed6e5232b 100644
--- a/libcxx/include/functional
+++ b/libcxx/include/functional
@@ -222,11 +222,16 @@ template<class Fn, class... BoundArgs>
 template<class R, class Fn, class... BoundArgs>
   constexpr unspecified bind(Fn&&, BoundArgs&&...);  // constexpr in C++20
 
+// [func.invoke]
 template<class F, class... Args>
  constexpr // constexpr in C++20
  invoke_result_t<F, Args...> invoke(F&& f, Args&&... args) // C++17
     noexcept(is_nothrow_invocable_v<F, Args...>);
 
+template<class R, class F, class... Args>
+  constexpr R invoke_r(F&& f, Args&&... args)              // C++23
+    noexcept(is_nothrow_invocable_r_v<R, F, Args...>);
+
 namespace placeholders {
   // M is the implementation-defined number of placeholders
   extern unspecified _1;

diff  --git a/libcxx/include/version b/libcxx/include/version
index 44a081a688303..2e5c17a0405ab 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -395,7 +395,7 @@ __cpp_lib_void_t                                        201411L <type_traits>
 # define __cpp_lib_constexpr_typeinfo                   202106L
 # define __cpp_lib_expected                             202202L
 # define __cpp_lib_forward_like                         202207L
-// # define __cpp_lib_invoke_r                             202106L
+# define __cpp_lib_invoke_r                             202106L
 # define __cpp_lib_is_scoped_enum                       202011L
 // # define __cpp_lib_move_only_function                   202110L
 # undef  __cpp_lib_optional

diff  --git a/libcxx/test/std/language.support/support.limits/support.limits.general/functional.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/functional.version.compile.pass.cpp
index 15c09201ced1e..d4845045c1711 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/functional.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/functional.version.compile.pass.cpp
@@ -325,17 +325,11 @@
 #   error "__cpp_lib_invoke should have the value 201411L in c++2b"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_invoke_r
-#     error "__cpp_lib_invoke_r should be defined in c++2b"
-#   endif
-#   if __cpp_lib_invoke_r != 202106L
-#     error "__cpp_lib_invoke_r should have the value 202106L in c++2b"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_invoke_r
-#     error "__cpp_lib_invoke_r should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_invoke_r
+#   error "__cpp_lib_invoke_r should be defined in c++2b"
+# endif
+# if __cpp_lib_invoke_r != 202106L
+#   error "__cpp_lib_invoke_r should have the value 202106L in c++2b"
 # endif
 
 # if !defined(_LIBCPP_VERSION)

diff  --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index b02679d734ae7..19735a40cd289 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -4207,17 +4207,11 @@
 #   error "__cpp_lib_invoke should have the value 201411L in c++2b"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_invoke_r
-#     error "__cpp_lib_invoke_r should be defined in c++2b"
-#   endif
-#   if __cpp_lib_invoke_r != 202106L
-#     error "__cpp_lib_invoke_r should have the value 202106L in c++2b"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_invoke_r
-#     error "__cpp_lib_invoke_r should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_invoke_r
+#   error "__cpp_lib_invoke_r should be defined in c++2b"
+# endif
+# if __cpp_lib_invoke_r != 202106L
+#   error "__cpp_lib_invoke_r should have the value 202106L in c++2b"
 # endif
 
 # ifndef __cpp_lib_is_aggregate

diff  --git a/libcxx/test/std/utilities/function.objects/func.invoke/invoke_feature_test_macro.pass.cpp b/libcxx/test/std/utilities/function.objects/func.invoke/invoke_feature_test_macro.pass.cpp
deleted file mode 100644
index b0404659d78c5..0000000000000
--- a/libcxx/test/std/utilities/function.objects/func.invoke/invoke_feature_test_macro.pass.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-// <functional>
-
-// template <class F, class ...Args>
-// result_of_t<F&&(Args&&...)> invoke(F&&, Args&&...);
-
-#include <functional>
-#include <cassert>
-
-#include "test_macros.h"
-
-#if TEST_STD_VER <= 14
-# ifdef __cpp_lib_invoke
-#   error Feature test macro should be defined
-# endif
-#else
-# ifndef __cpp_lib_invoke
-#   error Feature test macro not defined
-# endif
-# if __cpp_lib_invoke != 201411
-#   error __cpp_lib_invoke has the wrong value
-# endif
-#endif
-
-int foo(int) { return 42; }
-
-int main(int, char**) {
-#if defined(__cpp_lib_invoke)
-  assert(std::invoke(foo, 101) == 42);
-#endif
-
-  return 0;
-}

diff  --git a/libcxx/test/std/utilities/function.objects/func.invoke/invoke_r.pass.cpp b/libcxx/test/std/utilities/function.objects/func.invoke/invoke_r.pass.cpp
new file mode 100644
index 0000000000000..af4c0baf8c60c
--- /dev/null
+++ b/libcxx/test/std/utilities/function.objects/func.invoke/invoke_r.pass.cpp
@@ -0,0 +1,139 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <functional>
+
+// template<class R, class F, class... Args>
+// constexpr R invoke_r(F&& f, Args&&... args)              // C++23
+//     noexcept(is_nothrow_invocable_r_v<R, F, Args...>);
+
+#include <cassert>
+#include <concepts>
+#include <functional>
+#include <type_traits>
+#include <utility> // declval
+
+template <class R, class F, class ...Args>
+concept can_invoke_r = requires {
+    { std::invoke_r<R>(std::declval<F>(), std::declval<Args>()...) } -> std::same_as<R>;
+};
+
+constexpr bool test() {
+    // Make sure basic functionality works (i.e. we actually call the function and
+    // return the right result).
+    {
+        auto f = [](int i) { return i + 3; };
+        assert(std::invoke_r<int>(f, 4) == 7);
+    }
+
+    // Make sure invoke_r is SFINAE-friendly
+    {
+        auto f = [](int) -> char* { return nullptr; };
+        static_assert( can_invoke_r<char*, decltype(f), int>);
+        static_assert( can_invoke_r<void*, decltype(f), int>);
+        static_assert( can_invoke_r<void,  decltype(f), int>);   // discard return type
+        static_assert(!can_invoke_r<char*, decltype(f), void*>); // wrong argument type
+        static_assert(!can_invoke_r<char*, decltype(f)>);        // missing argument
+        static_assert(!can_invoke_r<int*,  decltype(f), int>);   // incompatible return type
+        static_assert(!can_invoke_r<void,  decltype(f), void*>); // discard return type, invalid argument type
+    }
+
+    // Make sure invoke_r has the right noexcept specification
+    {
+        auto f = [](int) noexcept(true) -> char* { return nullptr; };
+        auto g = [](int) noexcept(false) -> char* { return nullptr; };
+        struct ConversionNotNoexcept {
+            constexpr ConversionNotNoexcept(char*) noexcept(false) { }
+        };
+        static_assert( noexcept(std::invoke_r<char*>(f, 0)));
+        static_assert(!noexcept(std::invoke_r<char*>(g, 0)));                 // function call is not noexcept
+        static_assert(!noexcept(std::invoke_r<ConversionNotNoexcept>(f, 0))); // function call is noexcept, conversion isn't
+        static_assert(!noexcept(std::invoke_r<ConversionNotNoexcept>(g, 0))); // function call and conversion are both not noexcept
+    }
+
+    // Make sure invoke_r works with cv-qualified void return type
+    {
+        auto check = []<class CV_Void> {
+            bool was_called = false;
+            auto f = [&](int) -> char* { was_called = true; return nullptr; };
+            std::invoke_r<CV_Void>(f, 3);
+            assert(was_called);
+            static_assert(std::is_void_v<decltype(std::invoke_r<CV_Void>(f, 3))>);
+        };
+        check.template operator()<void>();
+        check.template operator()<void const>();
+        // volatile void is deprecated, so not testing it
+        // const volatile void is deprecated, so not testing it
+    }
+
+    // Make sure invoke_r forwards its arguments
+    {
+        struct NonCopyable {
+            NonCopyable() = default;
+            NonCopyable(NonCopyable const&) = delete;
+            NonCopyable(NonCopyable&&) = default;
+        };
+        // Forward argument, with void return
+        {
+            bool was_called = false;
+            auto f = [&](NonCopyable) { was_called = true; };
+            std::invoke_r<void>(f, NonCopyable());
+            assert(was_called);
+        }
+        // Forward argument, with non-void return
+        {
+            bool was_called = false;
+            auto f = [&](NonCopyable) -> int { was_called = true; return 0; };
+            std::invoke_r<int>(f, NonCopyable());
+            assert(was_called);
+        }
+        // Forward function object, with void return
+        {
+            struct MoveOnlyVoidFunction {
+                bool& was_called;
+                constexpr void operator()() && { was_called = true; }
+            };
+            bool was_called = false;
+            std::invoke_r<void>(MoveOnlyVoidFunction{was_called});
+            assert(was_called);
+        }
+        // Forward function object, with non-void return
+        {
+            struct MoveOnlyIntFunction {
+                bool& was_called;
+                constexpr int operator()() && { was_called = true; return 0; }
+            };
+            bool was_called = false;
+            std::invoke_r<int>(MoveOnlyIntFunction{was_called});
+            assert(was_called);
+        }
+    }
+
+    // Make sure invoke_r performs an implicit conversion of the result
+    {
+        struct Convertible {
+            constexpr operator int() const { return 42; }
+        };
+        auto f = []() -> Convertible { return Convertible{}; };
+        int result = std::invoke_r<int>(f);
+        assert(result == 42);
+    }
+
+    // Note: We don't test that `std::invoke_r` works with all kinds of callable types here,
+    //       since that is extensively tested in the `std::invoke` tests.
+
+    return true;
+}
+
+int main(int, char**) {
+    test();
+    static_assert(test());
+    return 0;
+}

diff  --git a/libcxx/test/std/utilities/function.objects/func.invoke/invoke_r.temporary.verify.cpp b/libcxx/test/std/utilities/function.objects/func.invoke/invoke_r.temporary.verify.cpp
new file mode 100644
index 0000000000000..2506ea0afc811
--- /dev/null
+++ b/libcxx/test/std/utilities/function.objects/func.invoke/invoke_r.temporary.verify.cpp
@@ -0,0 +1,31 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <functional>
+
+// template<class R, class F, class... Args>
+// constexpr R invoke_r(F&& f, Args&&... args)              // C++23
+//     noexcept(is_nothrow_invocable_r_v<R, F, Args...>);
+//
+// Make sure that we diagnose when std::invoke_r is used with a return type that
+// would yield a dangling reference to a temporary.
+
+// TODO: We currently can't diagnose because we don't implement reference_converts_from_temporary.
+// XFAIL: *
+
+#include <functional>
+#include <cassert>
+
+#include "test_macros.h"
+
+void f() {
+    auto func = []() -> int { return 0; };
+    std::invoke_r<int&&>(func); // expected-error {{Returning from invoke_r would bind a temporary object}}
+}

diff  --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index 4cf52fb88bbf1..16973203a639a 100755
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -391,7 +391,6 @@ def add_version_header(tc):
     "name": "__cpp_lib_invoke_r",
     "values": { "c++2b": 202106 },
     "headers": ["functional"],
-    "unimplemented": True,
   }, {
     "name": "__cpp_lib_is_aggregate",
     "values": { "c++17": 201703 },


        


More information about the libcxx-commits mailing list