Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add stdexec::scope_token
This diff adds a definition for `concept stdexec::scope_token` plus
tests confirming it accepts and rejects the expected things.

Co-authored-by: Eric Niebler <eniebler@nvidia.com>
  • Loading branch information
ispeters and ericniebler committed Feb 10, 2026
commit 86e22ef40a64637bb470eed2ee34b825025111b8
39 changes: 39 additions & 0 deletions include/stdexec/__detail/__scope_concepts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@

#include "__execution_fwd.hpp"

#include "__completion_signatures.hpp"
#include "__concepts.hpp"
#include "__operation_states.hpp"
#include "__receivers.hpp"
#include "__sender_concepts.hpp"

#include <exception>

namespace STDEXEC {
/////////////////////////////////////////////////////////////////////////////
Expand All @@ -30,4 +36,37 @@ namespace STDEXEC {
{ static_cast<bool>(assoc) } noexcept;
{ assoc.try_associate() } -> __std::same_as<_Assoc>;
};

namespace __scope_concepts {
struct __test_sender {
using sender_concept = STDEXEC::sender_t;

using completion_signatures = STDEXEC::completion_signatures<
STDEXEC::set_value_t(int),
STDEXEC::set_error_t(std::exception_ptr),
STDEXEC::set_stopped_t()
>;

struct __op {
using operation_state_concept = STDEXEC::operation_state_t;

__op() = default;
__op(__op&&) = delete;

void start() & noexcept {
}
};

template <class _Receiver>
__op connect(_Receiver) {
return {};
}
};
} // namespace __scope_concepts

template <class _Token>
concept scope_token = __std::copyable<_Token> && requires(const _Token token) {
{ token.try_associate() } -> scope_association;
{ token.wrap(__declval<__scope_concepts::__test_sender>()) } -> sender_in<STDEXEC::env<>>;
};
} // namespace STDEXEC
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ set(stdexec_test_sources
stdexec/concepts/test_concepts_sender.cpp
stdexec/concepts/test_concepts_stop_tokens.cpp
stdexec/concepts/test_concepts_scope_association.cpp
stdexec/concepts/test_concepts_scope_token.cpp
stdexec/concepts/test_awaitables.cpp
stdexec/algos/factories/test_just.cpp
stdexec/algos/factories/test_transfer_just.cpp
Expand Down
135 changes: 135 additions & 0 deletions test/stdexec/concepts/test_concepts_scope_token.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright (c) 2022 Ian Petersen
* Copyright (c) 2025 NVIDIA Corporation
*
* Licensed under the Apache License Version 2.0 with LLVM Exceptions
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* https://llvm.org/LICENSE.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <catch2/catch.hpp>
#include <stdexec/execution.hpp>

namespace ex = STDEXEC;

namespace {

TEST_CASE("Scope token helpers are correctly defined", "[concepts][scope_token]") {
// check the test-sender and test-env definitions are appropriate
STATIC_REQUIRE(ex::sender<ex::__scope_concepts::__test_sender>);
STATIC_REQUIRE(ex::sender_in<ex::__scope_concepts::__test_sender, ex::env<>>);
STATIC_REQUIRE(ex::operation_state<ex::__scope_concepts::__test_sender::__op>);
}

// a "null" token that can always create new associations
struct null_token {
// the always-truthy association type
struct assoc {
// this need not be explicit, although it should be
constexpr operator bool() const noexcept {
return true;
}

// this may throw, although it need not
constexpr assoc try_associate() const noexcept {
return {};
}
};

constexpr assoc try_associate() const noexcept {
return {};
}

template <ex::sender Sender>
Sender&& wrap(Sender&& snd) const noexcept {
return std::forward<Sender>(snd);
}
};

struct throwing_try_associate : null_token {
constexpr assoc try_associate() const noexcept(false) {
return {};
}
};

struct throwing_wrap : null_token {
template <ex::sender Sender>
constexpr Sender&& wrap(Sender&& snd) const noexcept(false) {
return std::forward<Sender>(snd);
}
};

struct wrapping_wrap : null_token {
template <ex::sender Sender>
struct wrapper : Sender {
wrapper(const Sender& snd)
: Sender(snd) {
}

wrapper(Sender&& snd)
: Sender(std::move(snd)) {
}
};

template <ex::sender Sender>
auto wrap(Sender&& snd) const noexcept {
return wrapper{std::forward<Sender>(snd)};
}
};

TEST_CASE("Scope token concept accepts basic token types", "[concepts][scope_token]") {
// scope_token should accept the basic null_token
STATIC_REQUIRE(ex::scope_token<null_token>);

// it's ok for try_associate to throw
STATIC_REQUIRE(ex::scope_token<throwing_try_associate>);

// it's ok for wrap to throw
STATIC_REQUIRE(ex::scope_token<throwing_wrap>);

// it's ok for wrap to change the type of its argument
STATIC_REQUIRE(ex::scope_token<wrapping_wrap>);
}

struct move_only : null_token {
move_only() = default;
move_only(move_only&&) = default;
~move_only() = default;

move_only& operator=(move_only&&) = default;
};

struct non_const_try_associate : null_token {
assoc try_associate() noexcept {
return {};
}
};

struct non_const_wrap : null_token {
template <ex::sender Sender>
Sender&& wrap(Sender&& snd) noexcept {
return std::forward<Sender>(snd);
}
};

TEST_CASE("Scope token concept rejects non-token types", "[concepts][scope_token]") {
STATIC_REQUIRE(!ex::scope_token<int>);

// tokens must be copyable
STATIC_REQUIRE(!ex::scope_token<move_only>);

// try_associate must be const-qualified
STATIC_REQUIRE(!ex::scope_token<non_const_try_associate>);

// wrap must be const-qualified
STATIC_REQUIRE(!ex::scope_token<non_const_wrap>);
}
} // namespace