Skip to content

Commit

Permalink
[libc++] Implement P2136R3 std::invoke_r
Browse files Browse the repository at this point in the history
Differential Revision: https://reviews.llvm.org/D143610
  • Loading branch information
ldionne committed Feb 13, 2023
1 parent afe9b0b commit 87cc95a
Show file tree
Hide file tree
Showing 11 changed files with 207 additions and 66 deletions.
2 changes: 1 addition & 1 deletion libcxx/docs/FeatureTestMacroTable.rst
Expand Up @@ -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``
------------------------------------------------- -----------------
Expand Down
2 changes: 1 addition & 1 deletion libcxx/docs/Status/Cxx2bPapers.csv
Expand Up @@ -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","",""
Expand Down
19 changes: 19 additions & 0 deletions libcxx/include/__functional/invoke.h
Expand Up @@ -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
5 changes: 5 additions & 0 deletions libcxx/include/functional
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion libcxx/include/version
Expand Up @@ -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
Expand Down
Expand Up @@ -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)
Expand Down
Expand Up @@ -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
Expand Down

This file was deleted.

@@ -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;
}
@@ -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}}
}
1 change: 0 additions & 1 deletion libcxx/utils/generate_feature_test_macro_components.py
Expand Up @@ -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 },
Expand Down

0 comments on commit 87cc95a

Please sign in to comment.