[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