Skip to content

Commit db2f61a

Browse files
committed
Add stdexec::scope_association
This diff adds a definition for `concept stdexec::scope_association` plus tests confirming it accepts and rejects the expected things.
1 parent de7420a commit db2f61a

File tree

6 files changed

+204
-0
lines changed

6 files changed

+204
-0
lines changed

include/stdexec/__detail/__concepts.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,12 @@ namespace stdexec {
253253
template <class... _Ts>
254254
concept __nothrow_copy_constructible = (__nothrow_constructible_from<_Ts, const _Ts&> && ...);
255255

256+
template <class _Ty, class _A>
257+
concept __nothrow_assignable_from = STDEXEC_IS_NOTHROW_ASSIGNABLE(_Ty, _A);
258+
259+
template <class... _Ts>
260+
concept __nothrow_move_assignable = (__nothrow_assignable_from<_Ts, _Ts> && ...);
261+
256262
template <class... _Ts>
257263
concept __decay_copyable = (constructible_from<__decay_t<_Ts>, _Ts> && ...);
258264

include/stdexec/__detail/__config.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,12 @@ namespace __coro = std::experimental;
356356
# define STDEXEC_IS_TRIVIALLY_CONSTRUCTIBLE(...) std::is_trivially_constructible_v<__VA_ARGS__>
357357
#endif
358358

359+
#if STDEXEC_HAS_BUILTIN(__is_nothrow_assignable) || STDEXEC_MSVC()
360+
# define STDEXEC_IS_NOTHROW_ASSIGNABLE(...) __is_nothrow_assignable(__VA_ARGS__)
361+
#else
362+
# define STDEXEC_IS_NOTHROW_ASSIGNABLE(...) std::is_nothrow_assignable_v<__VA_ARGS__>
363+
#endif
364+
359365
#if STDEXEC_HAS_BUILTIN(__is_empty) || STDEXEC_MSVC()
360366
# define STDEXEC_IS_EMPTY(...) __is_empty(__VA_ARGS__)
361367
#else
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (c) 2025 Ian Petersen
3+
*
4+
* Licensed under the Apache License Version 2.0 with LLVM Exceptions
5+
* (the "License"); you may not use this file except in compliance with
6+
* the License. You may obtain a copy of the License at
7+
*
8+
* https://llvm.org/LICENSE.txt
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
#pragma once
17+
18+
#include "__execution_fwd.hpp"
19+
20+
#include "__concepts.hpp"
21+
22+
namespace stdexec {
23+
/////////////////////////////////////////////////////////////////////////////
24+
// [exec.scope.concepts]
25+
template <class _Assoc>
26+
concept scope_association = movable<_Assoc> && __nothrow_move_constructible<_Assoc>
27+
&& __nothrow_move_assignable<_Assoc> && default_initializable<_Assoc>
28+
&& requires(const _Assoc assoc) {
29+
{ static_cast<bool>(assoc) } noexcept;
30+
{ assoc.try_associate() } -> same_as<_Assoc>;
31+
};
32+
} // namespace stdexec

include/stdexec/execution.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
#include "__detail/__run_loop.hpp" // IWYU pragma: export
4848
#include "__detail/__schedule_from.hpp" // IWYU pragma: export
4949
#include "__detail/__schedulers.hpp" // IWYU pragma: export
50+
#include "__detail/__scope_concepts.hpp" // IWYU pragma: export
5051
#include "__detail/__senders.hpp" // IWYU pragma: export
5152
#include "__detail/__sender_adaptor_closure.hpp" // IWYU pragma: export
5253
#include "__detail/__split.hpp" // IWYU pragma: export

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ set(stdexec_test_sources
2929
stdexec/concepts/test_concepts_receiver.cpp
3030
stdexec/concepts/test_concept_operation_state.cpp
3131
stdexec/concepts/test_concepts_sender.cpp
32+
stdexec/concepts/test_concepts_scope_association.cpp
3233
stdexec/concepts/test_awaitables.cpp
3334
stdexec/algos/factories/test_just.cpp
3435
stdexec/algos/factories/test_transfer_just.cpp
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
* Copyright (c) 2022 Ian Petersen
3+
*
4+
* Licensed under the Apache License Version 2.0 with LLVM Exceptions
5+
* (the "License"); you may not use this file except in compliance with
6+
* the License. You may obtain a copy of the License at
7+
*
8+
* https://llvm.org/LICENSE.txt
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include <catch2/catch.hpp>
18+
#include <stdexec/execution.hpp>
19+
20+
namespace ex = stdexec;
21+
22+
namespace {
23+
24+
// a "null" association that is always truthy and for which try_associate() always succeeds
25+
struct null_association {
26+
// this need not be explicit, although it should be
27+
constexpr operator bool() const noexcept {
28+
return true;
29+
}
30+
31+
// this may throw, although it need not
32+
constexpr null_association try_associate() const noexcept {
33+
return {};
34+
}
35+
};
36+
37+
// a CRTP base that lets us produce variations on null_associaton with the right return type on try_associate
38+
template <class Derived>
39+
struct crtp_association : null_association {
40+
constexpr Derived try_associate() const noexcept {
41+
return {};
42+
}
43+
};
44+
45+
struct throwing_specials : crtp_association<throwing_specials> {
46+
// it's ok for the non-move operators to throw
47+
throwing_specials() noexcept(false) = default;
48+
throwing_specials(const throwing_specials&) noexcept(false) = default;
49+
throwing_specials(throwing_specials&&) noexcept = default;
50+
~throwing_specials() = default;
51+
52+
throwing_specials& operator=(const throwing_specials&) noexcept(false) = default;
53+
throwing_specials& operator=(throwing_specials&&) noexcept = default;
54+
};
55+
56+
struct move_only : crtp_association<move_only> {
57+
// copy operations are not required
58+
move_only() = default;
59+
move_only(move_only&&) = default;
60+
~move_only() = default;
61+
62+
move_only& operator=(move_only&&) = default;
63+
};
64+
65+
struct explicit_bool : crtp_association<explicit_bool> {
66+
// the bool conversion may be explicit
67+
constexpr explicit operator bool() const noexcept {
68+
return true;
69+
}
70+
};
71+
72+
struct throwing_reassociate : crtp_association<throwing_reassociate> {
73+
// try_associate may throw
74+
constexpr throwing_reassociate try_associate() const noexcept(false) {
75+
return {};
76+
}
77+
};
78+
79+
TEST_CASE(
80+
"Scope association concept accepts basic association types",
81+
"[concepts][scope_association]") {
82+
// scope_association should accept the basic null_association
83+
STATIC_REQUIRE(ex::scope_association<null_association>);
84+
85+
// the default constructor and copy operations may throw
86+
STATIC_REQUIRE(ex::scope_association<throwing_specials>);
87+
// double check that we're testing what we think we are
88+
STATIC_REQUIRE(!ex::__nothrow_constructible_from<throwing_specials>);
89+
STATIC_REQUIRE(!ex::__nothrow_constructible_from<throwing_specials, const throwing_specials&>);
90+
STATIC_REQUIRE(!ex::__nothrow_assignable_from<throwing_specials, const throwing_specials&>);
91+
92+
// copy operations are not required
93+
STATIC_REQUIRE(ex::scope_association<move_only>);
94+
95+
// the bool conversion may be explicit
96+
STATIC_REQUIRE(ex::scope_association<explicit_bool>);
97+
98+
// try_associate may throw
99+
STATIC_REQUIRE(ex::scope_association<throwing_reassociate>);
100+
}
101+
102+
// invalid association because of immovability
103+
struct immovable : crtp_association<immovable> {
104+
immovable(immovable&&) = delete;
105+
};
106+
107+
// conditionally-invalid association because of throwing move operations
108+
template <bool ThrowingCtor, bool ThrowingAssign>
109+
struct throwing_moves : crtp_association<throwing_moves<ThrowingCtor, ThrowingAssign>> {
110+
throwing_moves() = default;
111+
throwing_moves(throwing_moves&&) noexcept(ThrowingCtor) = default;
112+
~throwing_moves() = default;
113+
114+
throwing_moves& operator=(throwing_moves&&) noexcept(ThrowingAssign) = default;
115+
};
116+
117+
// invalid assocation because of a throwing move constructor
118+
using throwing_move_ctor = throwing_moves<true, false>;
119+
// invalid assocation because of a throwing move assignment operator
120+
using throwing_move_assign = throwing_moves<false, true>;
121+
122+
// invalid assocation because of a missing default constructor
123+
struct missing_ctor : crtp_association<missing_ctor> {
124+
missing_ctor() = delete;
125+
};
126+
127+
// invalid assocation because of a throwing conversion to bool
128+
struct throwing_boolish : crtp_association<throwing_boolish> {
129+
constexpr explicit operator bool() const noexcept(false) {
130+
return true;
131+
}
132+
};
133+
134+
// invalid assocation because try_associate returns the wrong type
135+
struct cannot_reassociate : null_association { };
136+
137+
TEST_CASE(
138+
"Scope association concept rejects non-association types",
139+
"[concepts][scope_association]") {
140+
STATIC_REQUIRE(!ex::scope_association<int>);
141+
142+
// movability is required
143+
STATIC_REQUIRE(!ex::scope_association<immovable>);
144+
145+
// the move operations must be non-throwing
146+
STATIC_REQUIRE(!ex::scope_association<throwing_move_ctor>);
147+
STATIC_REQUIRE(!ex::scope_association<throwing_move_assign>);
148+
149+
// default initialization is required
150+
STATIC_REQUIRE(!ex::scope_association<missing_ctor>);
151+
152+
// conversion to bool must not throw
153+
STATIC_REQUIRE(!ex::scope_association<throwing_boolish>);
154+
155+
// try_associate must return an association
156+
STATIC_REQUIRE(!ex::scope_association<cannot_reassociate>);
157+
}
158+
} // namespace

0 commit comments

Comments
 (0)