diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst --- a/libcxx/docs/FeatureTestMacroTable.rst +++ b/libcxx/docs/FeatureTestMacroTable.rst @@ -322,7 +322,7 @@ ------------------------------------------------- ----------------- ``__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 --- a/libcxx/docs/Status/Cxx2bPapers.csv +++ b/libcxx/docs/Status/Cxx2bPapers.csv @@ -20,7 +20,7 @@ "`P1659R3 `__","LWG","starts_with and ends_with","June 2021","","","|ranges|" "`P1951R1 `__","LWG","Default Arguments for pair Forwarding Constructor","June 2021","|Complete|","14.0" "`P1989R2 `__","LWG","Range constructor for std::string_view","June 2021","|Complete|","14.0","|ranges|" -"`P2136R3 `__","LWG","invoke_r","June 2021","","" +"`P2136R3 `__","LWG","invoke_r","June 2021","|Complete|","17.0" "`P2166R1 `__","LWG","A Proposal to Prohibit std::basic_string and std::basic_string_view construction from nullptr","June 2021","|Complete|","13.0" "","","","","","","" "`P0288R9 `__","LWG","``any_invocable``","October 2021","","" diff --git a/libcxx/include/__functional/invoke.h b/libcxx/include/__functional/invoke.h --- a/libcxx/include/__functional/invoke.h +++ b/libcxx/include/__functional/invoke.h @@ -541,6 +541,25 @@ #endif // _LIBCPP_STD_VER > 14 +#if _LIBCPP_STD_VER >= 23 +template + 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(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 --- a/libcxx/include/functional +++ b/libcxx/include/functional @@ -222,11 +222,16 @@ template constexpr unspecified bind(Fn&&, BoundArgs&&...); // constexpr in C++20 +// [func.invoke] template constexpr // constexpr in C++20 invoke_result_t invoke(F&& f, Args&&... args) // C++17 noexcept(is_nothrow_invocable_v); +template + constexpr R invoke_r(F&& f, Args&&... args) // C++23 + noexcept(is_nothrow_invocable_r_v); + namespace placeholders { // M is the implementation-defined number of placeholders extern unspecified _1; diff --git a/libcxx/include/version b/libcxx/include/version --- a/libcxx/include/version +++ b/libcxx/include/version @@ -395,7 +395,7 @@ # 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 --- 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 --- 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 --- 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 -// -//===----------------------------------------------------------------------===// - -// - -// template -// result_of_t invoke(F&&, Args&&...); - -#include -#include - -#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 --- /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 + +// + +// template +// constexpr R invoke_r(F&& f, Args&&... args) // C++23 +// noexcept(is_nothrow_invocable_r_v); + +#include +#include +#include +#include +#include // declval + +template +concept can_invoke_r = requires { + { std::invoke_r(std::declval(), std::declval()...) } -> std::same_as; +}; + +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(f, 4) == 7); + } + + // Make sure invoke_r is SFINAE-friendly + { + auto f = [](int) -> char* { return nullptr; }; + static_assert( can_invoke_r); + static_assert( can_invoke_r); + static_assert( can_invoke_r); // discard return type + static_assert(!can_invoke_r); // wrong argument type + static_assert(!can_invoke_r); // missing argument + static_assert(!can_invoke_r); // incompatible return type + static_assert(!can_invoke_r); // 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(f, 0))); + static_assert(!noexcept(std::invoke_r(g, 0))); // function call is not noexcept + static_assert(!noexcept(std::invoke_r(f, 0))); // function call is noexcept, conversion isn't + static_assert(!noexcept(std::invoke_r(g, 0))); // function call and conversion are both not noexcept + } + + // Make sure invoke_r works with cv-qualified void return type + { + auto check = [] { + bool was_called = false; + auto f = [&](int) -> char* { was_called = true; return nullptr; }; + std::invoke_r(f, 3); + assert(was_called); + static_assert(std::is_void_v(f, 3))>); + }; + check.template operator()(); + check.template operator()(); + // 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(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(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(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(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(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 --- /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 + +// + +// template +// constexpr R invoke_r(F&& f, Args&&... args) // C++23 +// noexcept(is_nothrow_invocable_r_v); +// +// 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 +#include + +#include "test_macros.h" + +void f() { + auto func = []() -> int { return 0; }; + std::invoke_r(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 --- a/libcxx/utils/generate_feature_test_macro_components.py +++ b/libcxx/utils/generate_feature_test_macro_components.py @@ -391,7 +391,6 @@ "name": "__cpp_lib_invoke_r", "values": { "c++2b": 202106 }, "headers": ["functional"], - "unimplemented": True, }, { "name": "__cpp_lib_is_aggregate", "values": { "c++17": 201703 },