From c5603689135a9fbe2707e04b52e5528952132310 Mon Sep 17 00:00:00 2001 From: Michael Schellenberger Costa Date: Sun, 23 Jan 2022 13:14:07 +0100 Subject: [PATCH 1/8] Add _Callable and tag_invoke CPO --- stl/inc/xutility | 92 +++++++++ stl/inc/yvals_core.h | 3 +- tests/std/test.lst | 1 + .../P2300R2_executors_tag_invoke/env.lst | 4 + .../test.compile.pass.cpp | 186 ++++++++++++++++++ .../test.compile.pass.cpp | 14 ++ 6 files changed, 299 insertions(+), 1 deletion(-) create mode 100644 tests/std/tests/P2300R2_executors_tag_invoke/env.lst create mode 100644 tests/std/tests/P2300R2_executors_tag_invoke/test.compile.pass.cpp diff --git a/stl/inc/xutility b/stl/inc/xutility index 132dddb48be..9a7b18b30df 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -635,6 +635,98 @@ template using indirect_result_t = invoke_result_t<_Fn, iter_reference_t<_Its>...>; // clang-format on +#ifdef __cpp_lib_executors +template +struct _Non_associated_entity { + struct _Type { + using type = _Ty; + }; +}; + +template +using _Unassociate = typename _Non_associated_entity<_Ty>::_Type; + +template +using _Reassociate = typename _Ty::type; + +template +concept _Non_associated = same_as<_Ty, _Unassociate<_Reassociate<_Ty>>>; + + +template +concept _Callable = requires(_Fn&& __fn, _Args&&... __args) { + {_STD forward<_Fn>(__fn)(_STD forward<_Args>(__args)...)}; +}; + +template +concept _Nothrow_callable = requires(_Fn&& __fn, _Args&&... __args) { + { _STD forward<_Fn>(__fn)(_STD forward<_Args>(__args)...) } + noexcept; +}; + +template +using _Call_result_t = decltype(_STD declval<_Fn>()(_STD declval<_Args>()...)); +template +using tag_t = decay_t; + +namespace _Tag_invoke { + void tag_invoke(); + + template + concept _Tag_invocable_no_adl = _Has_class_or_enum_type<_Tag> && requires { + {tag_invoke(_STD declval<_Tag>(), _STD declval<_Args>()...)}; + }; + + template + concept _Nothrow_tag_invocable_no_adl = _Has_class_or_enum_type<_Tag> && requires { + { tag_invoke(_STD declval<_Tag>(), _STD declval<_Args>()...) } + noexcept; + }; + + template + concept _Tag_observable_no_adl = _Has_class_or_enum_type<_Tag> && requires { + { tag_invoke(_STD declval<_Tag>(), _STD declval>()) } + noexcept; + }; + + class _Cpo { + public: + template + requires _Tag_invocable_no_adl<_Tag, _Args...> + constexpr auto operator()(_Tag _Tag_, _Args&&... _Args_) const + noexcept(noexcept(tag_invoke(_STD declval<_Tag>(), _STD declval<_Args>()...))) + -> decltype(tag_invoke(_STD declval<_Tag>(), _STD declval<_Args>()...)) { + return tag_invoke(_STD forward<_Tag>(_Tag_), _STD forward<_Args>(_Args_)...); + } + }; +} // namespace _Tag_invoke + +inline namespace _Cpos { + inline constexpr _Tag_invoke::_Cpo tag_invoke; +} + +template +concept tag_invocable = _Tag_invoke::_Tag_invocable_no_adl<_Tag, _Args...>; + +template +concept nothrow_tag_invocable = _Tag_invoke::_Nothrow_tag_invocable_no_adl<_Tag, _Args...>; + +template +concept tag_observable = _Tag_invoke::_Tag_observable_no_adl<_Tag, _Arg>; + +template +struct tag_invoke_result {}; + +template + requires _Tag_invoke::_Tag_invocable_no_adl<_Tag, _Args...> +struct tag_invoke_result<_Tag, _Args...> { + using type = decltype(tag_invoke(_STD declval<_Tag>(), _STD declval<_Args>()...)); +}; + +template +using tag_invoke_result_t = decltype(tag_invoke(_STD declval<_Tag>(), _STD declval<_Args>()...)); +#endif // __cpp_lib_executors + #pragma warning(push) #pragma warning(disable : 5046) // '%s': Symbol involving type with internal linkage not defined #ifdef __clang__ diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index 40d7a928a07..3c8729d4592 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -1446,7 +1446,8 @@ #define __cpp_lib_byteswap 202110L #ifdef __cpp_lib_concepts -#define __cpp_lib_expected 202202L +#define __cpp_lib_executors 202306L +#define __cpp_lib_expected 202202L #endif // __cpp_lib_concepts #define __cpp_lib_invoke_r 202106L diff --git a/tests/std/test.lst b/tests/std/test.lst index 03282d2299b..fb1a78f1ce5 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -480,6 +480,7 @@ tests\P2136R3_invoke_r tests\P2162R2_std_visit_for_derived_classes_from_variant tests\P2231R1_complete_constexpr_optional_variant tests\P2273R3_constexpr_unique_ptr +tests\P2300R2_executors_tag_invoke tests\P2321R2_proxy_reference tests\P2401R0_conditional_noexcept_for_exchange tests\P2415R2_owning_view diff --git a/tests/std/tests/P2300R2_executors_tag_invoke/env.lst b/tests/std/tests/P2300R2_executors_tag_invoke/env.lst new file mode 100644 index 00000000000..18e2d7c71ec --- /dev/null +++ b/tests/std/tests/P2300R2_executors_tag_invoke/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_latest_matrix.lst diff --git a/tests/std/tests/P2300R2_executors_tag_invoke/test.compile.pass.cpp b/tests/std/tests/P2300R2_executors_tag_invoke/test.compile.pass.cpp new file mode 100644 index 00000000000..b6d8b3afe26 --- /dev/null +++ b/tests/std/tests/P2300R2_executors_tag_invoke/test.compile.pass.cpp @@ -0,0 +1,186 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +using namespace std; + +struct whatever {}; +struct with_const {}; +struct with_reference {}; +struct conversion_target {}; +struct with_conversion { + operator conversion_target() noexcept { + return {}; + } +}; +struct with_explicit_conversion { + explicit operator conversion_target() noexcept { + return {}; + } +}; + +struct noexcept_function {}; +struct noexcept_observable {}; + +namespace meow { + struct meow_tag_t {}; + struct meow_other_tag_t {}; + + auto tag_invoke(meow_tag_t, int) -> int; + auto tag_invoke(meow_tag_t, double) -> double; + auto tag_invoke(meow_tag_t, whatever, int) -> whatever; + auto tag_invoke(meow_tag_t, const with_const) -> const with_const; + auto tag_invoke(meow_tag_t, with_reference&) -> with_reference&; + auto tag_invoke(meow_tag_t, conversion_target) -> conversion_target; + auto tag_invoke(meow_tag_t, noexcept_function&&) noexcept -> noexcept_function; + auto tag_invoke(meow_tag_t, const noexcept_observable) noexcept -> noexcept_observable; + + namespace woof { + auto tag_invoke(meow_other_tag_t, long) -> int; + } +} // namespace meow + +namespace bark { + struct with_conversion_other_namespace { + operator conversion_target() noexcept { + return {}; + } + }; + + auto tag_invoke(meow::meow_other_tag_t, double) -> int; +} // namespace bark + +namespace non_associated { + struct meow { + template <_Non_associated T> + void operator()(T) {} + }; + + static_assert(!is_invocable_v); + static_assert(is_invocable_v>); + static_assert(same_as>>); + + constexpr void test_associated(auto); +} // namespace non_associated + +template +constexpr bool is_associated = false; +template +constexpr bool is_associated()))>> = true; + +static_assert(is_associated); +static_assert(!is_associated<_Unassociate>); + +namespace callable { + // Functions + static_assert(_Callable); + static_assert(!_Callable); + static_assert(_Callable); + static_assert(_Callable); + static_assert(_Callable); + static_assert(_Callable); + static_assert(_Callable); + static_assert(!_Callable); + + // Function pointers + static_assert(_Callable); + static_assert(!_Callable); + static_assert(_Callable); + static_assert(_Callable); + static_assert(_Callable); + static_assert(_Callable); + static_assert(_Callable); + static_assert(!_Callable); + + // Functors + static_assert(_Callable); + static_assert(!_Callable); + static_assert(_Callable); + static_assert(_Callable); + static_assert(_Callable); + static_assert(_Callable); + static_assert(_Callable); + static_assert(_Callable); + static_assert(!_Callable); +} // namespace callable + +// Matching declarations +static_assert(tag_invocable); +static_assert(tag_invocable); +static_assert(tag_invocable); +static_assert(tag_invocable); + +// Wrong number of arguments +static_assert(!tag_invocable); +static_assert(!tag_invocable); +static_assert(!tag_invocable); +static_assert(!tag_invocable); + +// With Conversions +static_assert(tag_invocable); +static_assert(tag_invocable); +static_assert(!tag_invocable); + +// With Qualifiers +static_assert(tag_invocable); +static_assert(tag_invocable); +static_assert(tag_invocable); + +// Need to bind to reference +// static_assert(!tag_invocable); // TODO MSVC permissive BUG +static_assert(tag_invocable); +// static_assert(!tag_invocable); // TODO MSVC permissive BUG +static_assert(!tag_invocable); +static_assert(!tag_invocable); +static_assert(!tag_invocable); + +// Need to bind qualifier +static_assert(tag_invocable); +static_assert(tag_invocable); +static_assert(!tag_invocable); +static_assert(!tag_invocable); + +static_assert(tag_invocable); +static_assert(tag_invocable); +static_assert(!tag_invocable); +static_assert(!tag_invocable); + +static_assert(tag_invocable); +static_assert(tag_invocable); +static_assert(!tag_invocable); +static_assert(!tag_invocable); + +// References can bind to values +static_assert(tag_invocable); +static_assert(tag_invocable); +static_assert(tag_invocable); +static_assert(tag_invocable); + +// Matching tag only +static_assert(!tag_invocable); + +// Only in associated namespaces +static_assert(!tag_invocable); +static_assert(!tag_invocable); + +// noexcept is properly handled +static_assert(!nothrow_tag_invocable); +static_assert(nothrow_tag_invocable); + +// tag_observable requires const arguments +static_assert(!tag_observable); +static_assert(!tag_observable); +static_assert(tag_observable); + +// result type is properly determined +static_assert(is_same_v, int>); +static_assert(is_same_v, double>); +static_assert(is_same_v, double>); +static_assert(is_same_v, int>); +static_assert(is_same_v, conversion_target>); +static_assert(is_same_v, conversion_target>); +static_assert(is_same_v, const with_const>); +static_assert(is_same_v, with_reference&>); + +int main() {} diff --git a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp index 83f3c86bf29..e5854220914 100644 --- a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp +++ b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp @@ -714,6 +714,20 @@ STATIC_ASSERT(__cpp_lib_execution == 201603L); #endif #endif +#if _HAS_CXX23 && !defined(__EDG__) // TRANSITION, EDG concepts support +#ifndef __cpp_lib_executors +#error __cpp_lib_executors is not defined +#elif __cpp_lib_executors != 202306L +#error __cpp_lib_executors is not 202306L +#else +STATIC_ASSERT(__cpp_lib_executors == 202306L); +#endif +#else +#ifdef __cpp_lib_executors +#error __cpp_lib_executors is defined +#endif +#endif + #if _HAS_CXX23 && !defined(__EDG__) // TRANSITION, EDG concepts support #ifndef __cpp_lib_expected #error __cpp_lib_expected is not defined From 7338dc25cfd6fb369a866db37a151a283030221c Mon Sep 17 00:00:00 2001 From: Michael Schellenberger Costa Date: Sun, 23 Jan 2022 13:21:04 +0100 Subject: [PATCH 2/8] Add concept _Moveable_value --- stl/inc/execution | 75 ++++++ stl/inc/yvals_core.h | 1 + tests/std/test.lst | 1 + .../tests/P2300R2_executors_scheduler/env.lst | 4 + .../test.compile.pass.cpp | 213 ++++++++++++++++++ 5 files changed, 294 insertions(+) create mode 100644 tests/std/tests/P2300R2_executors_scheduler/env.lst create mode 100644 tests/std/tests/P2300R2_executors_scheduler/test.compile.pass.cpp diff --git a/stl/inc/execution b/stl/inc/execution index 9df2458b2e2..21feb75d0d6 100644 --- a/stl/inc/execution +++ b/stl/inc/execution @@ -5021,6 +5021,81 @@ _FwdIt2 adjacent_difference(_ExPo&&, const _FwdIt1 _First, const _FwdIt1 _Last, _Get_unwrapped_n(_Dest, _Idl_distance<_FwdIt1>(_UFirst, _ULast)), _Pass_fn(_Diff_op))); return _Dest; } + +#ifdef __cpp_lib_executors +namespace execution { + template + concept _Movable_value = move_constructible> && constructible_from, _Ty>; + + template + concept _Scheduler_base = copy_constructible> // + && equality_comparable> // + && is_nothrow_copy_constructible_v> // + && is_nothrow_destructible_v> // + && noexcept(_STD declval>() == _STD declval>()); + + namespace _Schedule { + struct _Cpo { + template <_Scheduler_base _Scheduler> + requires tag_invocable<_Cpo, _Scheduler> + constexpr auto operator()(_Scheduler _Sched) const noexcept(nothrow_tag_invocable<_Cpo, _Scheduler>) { + return tag_invoke(*this, _STD forward<_Scheduler>(_Sched)); + } + }; + } // namespace _Schedule + using schedule_t = _Schedule::_Cpo; + + inline namespace _Cpos { + inline constexpr _Schedule::_Cpo schedule; + } // namespace _Cpos + + template + concept scheduler = _Scheduler_base<_Sender> && requires(_Sender&& __s) { + {_EXEC schedule(_STD forward<_Sender>(__s))}; + }; + + enum class forward_progress_guarantee { concurrent, parallel, weakly_parallel }; + + namespace _Get_forward_progress_guarantee { + struct _Cpo { + template + _NODISCARD constexpr auto operator()(_Scheduler&& _Sched) const noexcept { + if constexpr (tag_observable<_Cpo, _Scheduler>) { + return tag_invoke(*this, _STD as_const(_Sched)); + } else { + return forward_progress_guarantee::weakly_parallel; + } + } + }; + } // namespace _Get_forward_progress_guarantee + using get_forward_progress_guarantee_t = _Get_forward_progress_guarantee::_Cpo; + + inline namespace _Cpos { + inline constexpr _Get_forward_progress_guarantee::_Cpo get_forward_progress_guarantee; + } // namespace _Cpos +} // namespace execution + +namespace this_thread { + namespace _Execute_may_block_caller { + struct _Cpo { + template <_EXEC scheduler _Scheduler> + _NODISCARD constexpr bool operator()(_Scheduler&& _Sched) const noexcept { + if constexpr (tag_observable<_Cpo, _Scheduler> // + && same_as, bool>) { + return tag_invoke(*this, _STD as_const(_Sched)); + } else { + return true; + } + } + }; + } // namespace _Execute_may_block_caller + using execute_may_block_caller_t = _Execute_may_block_caller::_Cpo; + + inline namespace _Cpos { + inline constexpr _Execute_may_block_caller::_Cpo execute_may_block_caller; + } // namespace _Cpos +} // namespace this_thread +#endif // __cpp_lib_executors _STD_END #pragma pop_macro("new") _STL_RESTORE_CLANG_WARNINGS diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index 3c8729d4592..c04af364fcf 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -1538,6 +1538,7 @@ compiler option, or define _ALLOW_RTCc_IN_STL to acknowledge that you have recei #define _STD ::std:: #define _CHRONO ::std::chrono:: #define _RANGES ::std::ranges:: +#define _EXEC ::std::execution:: // We use the stdext (standard extension) namespace to contain extensions that are not part of the current standard #define _STDEXT_BEGIN namespace stdext { diff --git a/tests/std/test.lst b/tests/std/test.lst index fb1a78f1ce5..5d9f2722458 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -480,6 +480,7 @@ tests\P2136R3_invoke_r tests\P2162R2_std_visit_for_derived_classes_from_variant tests\P2231R1_complete_constexpr_optional_variant tests\P2273R3_constexpr_unique_ptr +tests\P2300R2_executors_scheduler tests\P2300R2_executors_tag_invoke tests\P2321R2_proxy_reference tests\P2401R0_conditional_noexcept_for_exchange diff --git a/tests/std/tests/P2300R2_executors_scheduler/env.lst b/tests/std/tests/P2300R2_executors_scheduler/env.lst new file mode 100644 index 00000000000..18e2d7c71ec --- /dev/null +++ b/tests/std/tests/P2300R2_executors_scheduler/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_latest_matrix.lst diff --git a/tests/std/tests/P2300R2_executors_scheduler/test.compile.pass.cpp b/tests/std/tests/P2300R2_executors_scheduler/test.compile.pass.cpp new file mode 100644 index 00000000000..add6a9227ba --- /dev/null +++ b/tests/std/tests/P2300R2_executors_scheduler/test.compile.pass.cpp @@ -0,0 +1,213 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +using namespace std; + +namespace _Movable_value { + struct aggregate {}; + static_assert(execution::_Movable_value); + static_assert(execution::_Movable_value); + static_assert(execution::_Movable_value); + static_assert(execution::_Movable_value); + + struct move_only { + move_only(const move_only&) = delete; + move_only& operator=(const move_only&) = delete; + }; + static_assert(!execution::_Movable_value); + static_assert(!execution::_Movable_value); + static_assert(!execution::_Movable_value); + static_assert(!execution::_Movable_value); + struct copy_only { + copy_only(copy_only&&) = delete; + copy_only& operator=(copy_only&&) = delete; + }; + static_assert(!execution::_Movable_value); + static_assert(!execution::_Movable_value); + static_assert(!execution::_Movable_value); + static_assert(!execution::_Movable_value); + + struct not_assignable { + not_assignable& operator=(const not_assignable&) = delete; + not_assignable& operator=(not_assignable&&) = delete; + }; + static_assert(!execution::_Movable_value); + static_assert(!execution::_Movable_value); + static_assert(!execution::_Movable_value); + static_assert(!execution::_Movable_value); + + struct not_constructible { + not_constructible(const not_constructible&) = delete; + not_constructible(not_constructible&&) = delete; + }; + static_assert(!execution::_Movable_value); + static_assert(!execution::_Movable_value); + static_assert(!execution::_Movable_value); + static_assert(!execution::_Movable_value); +} // namespace _Movable_value + +namespace scheduler { + enum class IsCopyConstructible : bool { No, Yes }; + enum class IsEqualityComparable : bool { No, Yes }; + enum class IsNothrowCopyConstructible : bool { No, Yes }; + enum class IsNothrowDestructible : bool { No, Yes }; + enum class IsNothrowSwappable : bool { No, Yes }; + enum class IsNothrowComparable : bool { No, Yes }; + enum class IsSchedulable : bool { No, Yes }; + enum class IsNothrowSchedulable : bool { No, Yes }; + + template + struct tester { + constexpr tester() = default; + constexpr tester(const tester&) noexcept(NothrowCopyConstructible == IsNothrowCopyConstructible::Yes) requires( + CopyConstructible == IsCopyConstructible::Yes) = default; + constexpr ~tester() noexcept(NothrowDestructible == IsNothrowDestructible::Yes) = default; + friend bool operator==(const tester&, const tester&) noexcept(NothrowComparable == IsNothrowComparable::Yes) // + requires(EqualityComparable == IsEqualityComparable::Yes) = default; + + friend constexpr auto tag_invoke(execution::schedule_t, tester) noexcept( + NothrowSchedulable == IsNothrowSchedulable::Yes) requires(Schedulable == IsSchedulable::Yes) { + return true; + } + }; + + // Test basic constraints + static_assert(execution::scheduler>); + static_assert(!execution::scheduler>); + static_assert(!execution::scheduler>); + static_assert(!execution::scheduler< + tester>); + static_assert(!execution::scheduler>); + static_assert(!execution::scheduler>); + + // Test remove_cvref_t + static_assert(execution::scheduler&>); + static_assert(execution::scheduler&>); + static_assert(execution::scheduler&&>); + static_assert(execution::scheduler&&>); + + static_assert(execution::scheduler>); + static_assert(!execution::scheduler>); + static_assert(!execution::scheduler>); + + // Scheduling + static_assert(execution::scheduler>); + static_assert(execution::scheduler>); + static_assert(execution::scheduler< + tester>); + static_assert( + !execution::scheduler>); + + // Ensure that even when there is a tag_invoke that we still bail if scheduler is not satisfied + static_assert(!execution::scheduler>); + + // Test the actual CPOs + static_assert(invocable>); + static_assert(execution::schedule(tester{})); +} // namespace scheduler + +namespace forward_progress_guarantee { + enum class IsSchedulable : bool { No, Yes }; + enum class IsNothrowSchedulable : bool { No, Yes }; + enum class IsConstSchedulable : bool { No, Yes }; + + template + struct tester { + friend bool operator==(const tester&, const tester&) noexcept = default; + friend constexpr auto tag_invoke(execution::schedule_t, tester) noexcept + requires(Schedulable == IsSchedulable::Yes) { + return true; + } + + friend constexpr auto tag_invoke(execution::get_forward_progress_guarantee_t, const tester) noexcept( + NothrowSchedulable == IsNothrowSchedulable::Yes) requires(ConstSchedulable == IsConstSchedulable::Yes) { + return execution::forward_progress_guarantee::concurrent; + } + + friend constexpr auto tag_invoke(execution::get_forward_progress_guarantee_t, tester&&) noexcept( + NothrowSchedulable == IsNothrowSchedulable::Yes) requires(ConstSchedulable != IsConstSchedulable::Yes) { + return execution::forward_progress_guarantee::parallel; + } + }; + + static_assert(invocable>); + static_assert(execution::get_forward_progress_guarantee(tester{}) + == execution::forward_progress_guarantee::concurrent); + + // We need to fullfil scheduler + static_assert(!invocable>); + + // Ensure that we fall back to execution::forward_progress_guarantee::weakly_parallel + static_assert(execution::get_forward_progress_guarantee( + tester{}) + == execution::forward_progress_guarantee::weakly_parallel); + static_assert(execution::get_forward_progress_guarantee(tester{}) + == execution::forward_progress_guarantee::weakly_parallel); +} // namespace forward_progress_guarantee + +namespace execute_may_block_caller { + enum class IsSchedulable : bool { No, Yes }; + enum class IsNothrowSchedulable : bool { No, Yes }; + enum class IsConstSchedulable : bool { No, Yes }; + enum class DoesReturnsBool : bool { No, Yes }; + + template + struct tester { + friend bool operator==(const tester&, const tester&) noexcept = default; + friend constexpr auto tag_invoke(execution::schedule_t, tester) noexcept + requires(Schedulable == IsSchedulable::Yes) { + return true; + } + + friend constexpr auto tag_invoke(this_thread::execute_may_block_caller_t, const tester) noexcept( + NothrowSchedulable == IsNothrowSchedulable::Yes) requires(ConstSchedulable == IsConstSchedulable::Yes) { + if constexpr (ReturnsBool == DoesReturnsBool::Yes) { + return false; + } else { + return 0; + } + } + + friend constexpr auto tag_invoke(this_thread::execute_may_block_caller_t, tester&&) noexcept( + NothrowSchedulable == IsNothrowSchedulable::Yes) requires(ConstSchedulable != IsConstSchedulable::Yes) { + if constexpr (ReturnsBool == DoesReturnsBool::Yes) { + return false; + } else { + return 0; + } + } + }; + + static_assert(invocable>); + static_assert(!this_thread::execute_may_block_caller(tester{})); + + // We need to fullfil scheduler + static_assert(!invocable>); + + // Ensure that we fall back to true + static_assert(this_thread::execute_may_block_caller(tester{})); + static_assert(this_thread::execute_may_block_caller( + tester{})); + static_assert(this_thread::execute_may_block_caller( + tester{})); +} // namespace execute_may_block_caller + +int main() {} From 5deabc4cdfbcee3b6b100bdf40bba2869ca79760 Mon Sep 17 00:00:00 2001 From: Michael Schellenberger Costa Date: Sun, 23 Jan 2022 13:21:04 +0100 Subject: [PATCH 3/8] Implement Receivers [exec.recv] --- stl/inc/execution | 121 +++++++ stl/inc/stop_token | 7 + tests/std/test.lst | 1 + .../tests/P2300R2_executors_receiver/env.lst | 4 + .../test.compile.pass.cpp | 337 ++++++++++++++++++ 5 files changed, 470 insertions(+) create mode 100644 tests/std/tests/P2300R2_executors_receiver/env.lst create mode 100644 tests/std/tests/P2300R2_executors_receiver/test.compile.pass.cpp diff --git a/stl/inc/execution b/stl/inc/execution index 21feb75d0d6..14912ac4a79 100644 --- a/stl/inc/execution +++ b/stl/inc/execution @@ -5095,6 +5095,127 @@ namespace this_thread { inline constexpr _Execute_may_block_caller::_Cpo execute_may_block_caller; } // namespace _Cpos } // namespace this_thread + +namespace execution { + namespace _Set_value { + struct _Cpo { + // clang-format off + template <_Movable_value _Receiver, class... _Types> + requires tag_invocable<_Cpo, _Receiver, _Types...> + _NODISCARD constexpr auto operator()(_Receiver&& _Rec, _Types&&... _Args) const noexcept( + nothrow_tag_invocable<_Cpo, _Receiver, _Types...>) { + // clang-format on + return tag_invoke(*this, _STD forward<_Receiver>(_Rec), _STD forward<_Types>(_Args)...); + } + }; + } // namespace _Set_value + using set_value_t = _Set_value::_Cpo; + + inline namespace _Cpos { + inline constexpr _Set_value::_Cpo set_value; + } + + namespace _Set_error { + struct _Cpo { + // clang-format off + template <_Movable_value _Receiver, class _Exception> + requires tag_invocable<_Cpo, _Receiver, _Exception> + _NODISCARD constexpr auto operator()(_Receiver&& _Rec, _Exception&& _Except) const + noexcept(nothrow_tag_invocable<_Cpo, _Receiver, _Exception>) { + // clang-format on + return tag_invoke(*this, _STD forward<_Receiver>(_Rec), _STD forward<_Exception>(_Except)); + } + }; + } // namespace _Set_error + using set_error_t = _Set_error::_Cpo; + + inline namespace _Cpos { + inline constexpr _Set_error::_Cpo set_error; + } + + namespace _Set_done { + struct _Cpo { + // clang-format off + template <_Movable_value _Receiver> + requires tag_invocable<_Cpo, _Receiver> + _NODISCARD constexpr auto operator()(_Receiver&& _Rec) const + noexcept(nothrow_tag_invocable<_Cpo, _Receiver>) { + // clang-format on + return tag_invoke(*this, _STD forward<_Receiver>(_Rec)); + } + }; + } // namespace _Set_done + using set_done_t = _Set_done::_Cpo; + + inline namespace _Cpos { + inline constexpr _Set_done::_Cpo set_done; + } + + // clang-format off + template + concept receiver = _Movable_value<_Ty> && requires (remove_cvref_t<_Ty>&& __t, _Exception&& __e) { + { _EXEC set_done(_STD move(__t)) } noexcept; + { _EXEC set_error(_STD move(__t), _STD forward<_Exception>(__e)) } noexcept; + }; + + template + concept receiver_of = receiver<_Ty> && requires (remove_cvref_t<_Ty>&& __t, _Types&&... __args) { + { _EXEC set_value(_STD move(__t), _STD forward<_Types>(__args)...) }; + }; + // clang-format on + + namespace _Get_scheduler { + struct _Cpo { + // clang-format off + template + requires tag_observable<_Cpo, _Receiver> + _NODISCARD constexpr auto operator()(_Receiver&& _Rec) const noexcept { + // clang-format on + return tag_invoke(*this, _STD as_const(_Rec)); + } + }; + } // namespace _Get_scheduler + using get_scheduler_t = _Get_scheduler::_Cpo; + + inline namespace _Cpos { + inline constexpr _Get_scheduler::_Cpo get_scheduler; + } + + namespace _Get_allocator { + struct _Cpo { + // clang-format off + template + requires tag_observable<_Cpo, _Receiver> + _NODISCARD constexpr auto operator()(_Receiver&& _Rec) const noexcept { + // clang-format on + return tag_invoke(*this, _STD as_const(_Rec)); + } + }; + } // namespace _Get_allocator + using get_allocator_t = _Get_allocator::_Cpo; + + inline namespace _Cpos { + inline constexpr _Get_allocator::_Cpo get_allocator; + } + + namespace _Get_stop_token { + struct _Cpo { + template + _NODISCARD constexpr auto operator()(_Receiver&& _Rec) const noexcept { + if constexpr (tag_observable<_Cpo, _Receiver>) { + return tag_invoke(*this, _STD as_const(_Rec)); + } else { + return never_stop_token{}; + } + } + }; + } // namespace _Get_stop_token + using get_stop_token_t = _Get_stop_token::_Cpo; + + inline namespace _Cpos { + inline constexpr _Get_stop_token::_Cpo get_stop_token; + } +} // namespace execution #endif // __cpp_lib_executors _STD_END #pragma pop_macro("new") diff --git a/stl/inc/stop_token b/stl/inc/stop_token index c60b5d401cf..9404d8fd6b3 100644 --- a/stl/inc/stop_token +++ b/stl/inc/stop_token @@ -388,6 +388,13 @@ private: template stop_callback(stop_token, _Callback) -> stop_callback<_Callback>; +#ifdef __cpp_lib_executors +// TRANSITION: Implement stop token part +class never_stop_token { + _NODISCARD friend constexpr bool operator==(never_stop_token, never_stop_token) = default; +}; +#endif // __cpp_lib_executors + _STD_END #pragma pop_macro("new") _STL_RESTORE_CLANG_WARNINGS diff --git a/tests/std/test.lst b/tests/std/test.lst index 5d9f2722458..becc34b1716 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -480,6 +480,7 @@ tests\P2136R3_invoke_r tests\P2162R2_std_visit_for_derived_classes_from_variant tests\P2231R1_complete_constexpr_optional_variant tests\P2273R3_constexpr_unique_ptr +tests\P2300R2_executors_receiver tests\P2300R2_executors_scheduler tests\P2300R2_executors_tag_invoke tests\P2321R2_proxy_reference diff --git a/tests/std/tests/P2300R2_executors_receiver/env.lst b/tests/std/tests/P2300R2_executors_receiver/env.lst new file mode 100644 index 00000000000..18e2d7c71ec --- /dev/null +++ b/tests/std/tests/P2300R2_executors_receiver/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_latest_matrix.lst diff --git a/tests/std/tests/P2300R2_executors_receiver/test.compile.pass.cpp b/tests/std/tests/P2300R2_executors_receiver/test.compile.pass.cpp new file mode 100644 index 00000000000..7073e405bee --- /dev/null +++ b/tests/std/tests/P2300R2_executors_receiver/test.compile.pass.cpp @@ -0,0 +1,337 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +using namespace std; + +namespace set_value { + enum class IsMovable : bool { No, Yes }; + + template + struct tester { + tester() = default; + tester(const tester&) noexcept = default; + tester(tester&&) requires(Movable == IsMovable::No) = delete; + + friend constexpr auto tag_invoke(execution::set_value_t, tester, int) noexcept { + return true; + } + }; + + // Test whether we can tag invoke + static_assert(invocable, int>); + static_assert(!invocable, int>); + + // Ensure we got the right number of arguments + static_assert(!invocable, int, double>); + static_assert(!invocable>); + + // Actually call it + static_assert(execution::set_value(tester{}, 5)); +} // namespace set_value + +namespace set_error { + enum class IsMovable : bool { No, Yes }; + struct Error {}; + + template + struct tester { + tester() = default; + tester(const tester&) noexcept = default; + tester(tester&&) requires(Movable == IsMovable::No) = delete; + + friend constexpr auto tag_invoke(execution::set_error_t, tester, Error) noexcept { + return true; + } + }; + + // Test whether we can tag invoke + static_assert(invocable, Error>); + static_assert(!invocable, Error>); + + // Ensure we got the right number of arguments + static_assert(!invocable, Error, double>); + static_assert(!invocable>); + + // Actually call it + static_assert(execution::set_error(tester{}, Error{})); +} // namespace set_error + +namespace set_done { + enum class IsMovable : bool { No, Yes }; + struct Error {}; + + template + struct tester { + tester() = default; + tester(const tester&) noexcept = default; + tester(tester&&) requires(Movable == IsMovable::No) = delete; + + friend constexpr auto tag_invoke(execution::set_done_t, tester) noexcept { + return true; + } + }; + + // Test whether we can tag invoke + static_assert(invocable>); + static_assert(!invocable>); + + // Ensure we got the right number of arguments + static_assert(!invocable, int>); + + // Actually call it + static_assert(execution::set_done(tester{})); +} // namespace set_done + +namespace receiver { + enum class IsMovable : bool { No, Yes }; + enum class HasSetDone : bool { No, Yes }; + enum class HasSetError : bool { No, Yes }; + struct Error {}; + struct OtherError {}; + + template + struct tester { + tester() = default; + tester(const tester&) noexcept = default; + tester(tester&&) requires(Movable == IsMovable::No) = delete; + + friend constexpr auto tag_invoke(execution::set_done_t, tester) noexcept requires(SetDone == HasSetDone::Yes) { + return true; + } + + friend constexpr auto tag_invoke(execution::set_error_t, tester, Error) noexcept + requires(SetError == HasSetError::Yes) { + return true; + } + }; + + // No move no receiver + static_assert(execution::receiver, Error>); + static_assert(!execution::receiver, Error>); + + // Must have set_done + static_assert(!execution::receiver, Error>); + + // Must have set_error + static_assert(!execution::receiver, Error>); + + // Must pass the right error set_error + static_assert(!execution::receiver, OtherError>); +} // namespace receiver + +namespace receiver_of { + enum class IsMovable : bool { No, Yes }; + enum class HasSetDone : bool { No, Yes }; + enum class HasSetError { Yes, No, Custom }; + enum class HasSetCustomError : bool { No, Yes }; + enum class HasSetValue : bool { No, Yes }; + class CustomError {}; + + template + struct tester { + tester() = default; + tester(const tester&) noexcept = default; + tester(tester&&) requires(Movable == IsMovable::No) = delete; + + friend constexpr auto tag_invoke(execution::set_done_t, tester) noexcept requires(SetDone == HasSetDone::Yes) { + return true; + } + + friend auto tag_invoke(execution::set_error_t, tester, exception_ptr) noexcept + requires(SetError == HasSetError::Yes) { + return true; + } + + friend constexpr auto tag_invoke(execution::set_error_t, tester, CustomError) noexcept + requires(SetError == HasSetError::Custom) { + return true; + } + + friend constexpr auto tag_invoke(execution::set_value_t, tester, int) noexcept + requires(SetValue == HasSetValue::Yes) { + return true; + } + }; + + // No move no receiver + static_assert(execution::receiver_of, int>); + static_assert(!execution::receiver_of, int>); + + // Must have set_done + static_assert(!execution::receiver_of, int>); + + // Must have set_error + static_assert(!execution::receiver_of, int>); + + // Must have right set_error + static_assert(!execution::receiver_of, int>); + + // Must have set_value + static_assert( + !execution::receiver_of, int>); + + // Must have the right set_value + static_assert(!execution::receiver_of, nullptr_t>); +} // namespace receiver_of + +namespace get_scheduler { + enum class IsMovable : bool { No, Yes }; + enum class HasSetDone : bool { No, Yes }; + enum class HasSetError : bool { No, Yes }; + enum class IsNothrowObservable : bool { No, Yes }; + enum class IsConstObservable : bool { No, Yes }; + + template + struct tester { + tester() = default; + tester(const tester&) noexcept = default; + tester(tester&&) requires(Movable == IsMovable::No) = delete; + + friend constexpr auto tag_invoke(execution::set_done_t, tester) noexcept requires(SetDone == HasSetDone::Yes) { + return true; + } + + friend auto tag_invoke(execution::set_error_t, tester, exception_ptr) noexcept + requires(SetError == HasSetError::Yes) { + return true; + } + + friend constexpr auto tag_invoke(execution::get_scheduler_t, const tester) noexcept( + NothrowObservable == IsNothrowObservable::Yes) requires(ConstObservable == IsConstObservable::Yes) { + return true; + } + + friend constexpr auto tag_invoke(execution::get_scheduler_t, tester&&) noexcept( + NothrowObservable == IsNothrowObservable::Yes) requires(ConstObservable == IsConstObservable::No) { + return false; + } + }; + + // Ensure we require a receiver + static_assert(invocable>); + static_assert(!invocable>); + static_assert(!invocable>); + static_assert(!invocable>); + + // Ensure we satisfy observable + static_assert(!invocable>); + static_assert(!invocable>); + + // Actually call it + static_assert(execution::get_scheduler(tester{})); +} // namespace get_scheduler + +namespace get_allocator { + enum class IsMovable : bool { No, Yes }; + enum class HasSetDone : bool { No, Yes }; + enum class HasSetError : bool { No, Yes }; + enum class IsNothrowObservable : bool { No, Yes }; + enum class IsConstObservable : bool { No, Yes }; + + template + struct tester { + tester() = default; + tester(const tester&) noexcept = default; + tester(tester&&) requires(Movable == IsMovable::No) = delete; + + friend constexpr auto tag_invoke(execution::set_done_t, tester) noexcept requires(SetDone == HasSetDone::Yes) { + return true; + } + + friend auto tag_invoke(execution::set_error_t, tester, exception_ptr) noexcept + requires(SetError == HasSetError::Yes) { + return true; + } + + friend constexpr auto tag_invoke(execution::get_allocator_t, const tester) noexcept( + NothrowObservable == IsNothrowObservable::Yes) requires(ConstObservable == IsConstObservable::Yes) { + return true; + } + + friend constexpr auto tag_invoke(execution::get_allocator_t, tester&&) noexcept( + NothrowObservable == IsNothrowObservable::Yes) requires(ConstObservable == IsConstObservable::No) { + return false; + } + }; + + // Ensure we require a receiver + static_assert(invocable>); + static_assert(!invocable>); + static_assert(!invocable>); + static_assert(!invocable>); + + // Ensure we satisfy observable + static_assert(!invocable>); + static_assert(!invocable>); + + // Actually call it + static_assert(execution::get_allocator(tester{})); +} // namespace get_allocator + +namespace get_stop_token { + enum class IsMovable : bool { No, Yes }; + enum class HasSetDone : bool { No, Yes }; + enum class HasSetError : bool { No, Yes }; + enum class IsNothrowObservable : bool { No, Yes }; + enum class IsConstObservable : bool { No, Yes }; + + template + struct tester { + tester() = default; + tester(const tester&) noexcept = default; + tester(tester&&) requires(Movable == IsMovable::No) = delete; + + friend constexpr auto tag_invoke(execution::set_done_t, tester) noexcept requires(SetDone == HasSetDone::Yes) { + return true; + } + + friend auto tag_invoke(execution::set_error_t, tester, exception_ptr) noexcept + requires(SetError == HasSetError::Yes) { + return true; + } + + friend constexpr auto tag_invoke(execution::get_stop_token_t, const tester) noexcept( + NothrowObservable == IsNothrowObservable::Yes) requires(ConstObservable == IsConstObservable::Yes) { + return true; + } + + friend constexpr auto tag_invoke(execution::get_stop_token_t, tester&&) noexcept( + NothrowObservable == IsNothrowObservable::Yes) requires(ConstObservable == IsConstObservable::No) { + return false; + } + }; + + // Ensure we require a receiver + static_assert(invocable>); + static_assert(!invocable>); + static_assert(!invocable>); + static_assert(!invocable>); + + // Ensure that when we are not observable, we return never_stop_token + static_assert( + execution::get_stop_token(tester{}) + == never_stop_token{}); + static_assert(execution::get_stop_token(tester{}) + == never_stop_token{}); + + // Actually call it + static_assert(execution::get_stop_token(tester{})); +} // namespace get_stop_token + +int main() {} From 951b5301275517b1c169831fbe78a221b23917c0 Mon Sep 17 00:00:00 2001 From: Michael Schellenberger Costa Date: Sun, 23 Jan 2022 13:21:04 +0100 Subject: [PATCH 4/8] Implement start CPO and operation_state concept --- stl/inc/execution | 24 +++++++++++ .../test.compile.pass.cpp | 42 +++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/stl/inc/execution b/stl/inc/execution index 14912ac4a79..70f4e487026 100644 --- a/stl/inc/execution +++ b/stl/inc/execution @@ -5215,6 +5215,30 @@ namespace execution { inline namespace _Cpos { inline constexpr _Get_stop_token::_Cpo get_stop_token; } + + namespace _Operation_state_start { + struct _Cpo { + // clang-format off + template + requires destructible<_Ty> && is_object_v<_Ty> && nothrow_tag_invocable<_Cpo, _Ty&> + _NODISCARD constexpr auto operator()(_Ty& _State) const noexcept { + // clang-format on + return tag_invoke(*this, _State); + } + }; + } // namespace _Operation_state_start + using start_t = _Operation_state_start::_Cpo; + + inline namespace _Cpos { + inline constexpr _Operation_state_start::_Cpo start; + } + + template + concept operation_state = destructible<_Ty> && is_object_v<_Ty> && requires(_Ty& __t) { + // clang-format off + {_EXEC start(__t)} noexcept; + // clang-format on + }; } // namespace execution #endif // __cpp_lib_executors _STD_END diff --git a/tests/std/tests/P2300R2_executors_receiver/test.compile.pass.cpp b/tests/std/tests/P2300R2_executors_receiver/test.compile.pass.cpp index 7073e405bee..2366ec5b9ba 100644 --- a/tests/std/tests/P2300R2_executors_receiver/test.compile.pass.cpp +++ b/tests/std/tests/P2300R2_executors_receiver/test.compile.pass.cpp @@ -334,4 +334,46 @@ namespace get_stop_token { static_assert(execution::get_stop_token(tester{})); } // namespace get_stop_token +namespace operation_state { + enum class IsDestructible : bool { No, Yes }; + enum class HasStart { No, LValue, LValueMayThrow, RValue, RValueMayThrow }; + + template + struct tester { + tester() = default; + ~tester() noexcept(Destructible == IsDestructible::Yes){}; + + friend constexpr void tag_invoke(execution::start_t, tester&) noexcept requires(Start == HasStart::LValue) {} + friend constexpr void tag_invoke(execution::start_t, tester&) requires(Start == HasStart::LValueMayThrow) {} + + friend constexpr void tag_invoke(execution::start_t, tester&&) noexcept requires(Start == HasStart::RValue) {} + friend constexpr void tag_invoke(execution::start_t, tester&&) requires(Start == HasStart::RValueMayThrow) {} + }; + + static_assert(execution::operation_state>); + static_assert(invocable&>); + + // Needs to be destructible + static_assert(!execution::operation_state>); + static_assert(!invocable>); + + // Should be an object + static_assert(!execution::operation_state&>); + + // Must have `start` operation + static_assert(!execution::operation_state>); + static_assert(!invocable>); + + // Must take lvalue argument for start even if tag_invocable for rvalue + static_assert(!execution::operation_state>); + static_assert(!invocable>); + static_assert(tag_invocable>); + + // Must have noexcept `start_t` + static_assert(!execution::operation_state>); + static_assert(!invocable>); + static_assert(!execution::operation_state>); + static_assert(!invocable>); +} // namespace operation_state + int main() {} From 7c47209eac1b892dccd1d667a601cdd108b90c38 Mon Sep 17 00:00:00 2001 From: Michael Schellenberger Costa Date: Sun, 23 Jan 2022 13:21:04 +0100 Subject: [PATCH 5/8] Implement Senders [exec.snd] --- stl/inc/execution | 137 +++++++++++ tests/std/test.lst | 1 + .../tests/P2300R2_executors_sender/env.lst | 4 + .../test.compile.pass.cpp | 213 ++++++++++++++++++ 4 files changed, 355 insertions(+) create mode 100644 tests/std/tests/P2300R2_executors_sender/env.lst create mode 100644 tests/std/tests/P2300R2_executors_sender/test.compile.pass.cpp diff --git a/stl/inc/execution b/stl/inc/execution index 70f4e487026..87a8f18882e 100644 --- a/stl/inc/execution +++ b/stl/inc/execution @@ -5239,6 +5239,143 @@ namespace execution { {_EXEC start(__t)} noexcept; // clang-format on }; + + template