Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion include/nvexec/stream/sync_wait.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ namespace nv::execution::_strm
struct sync_wait_t
{
template <sender_in<env> Sender>
requires __single_value_variant_sender<Sender, env>
requires(__count_of<set_value_t, Sender, env>::value == 1)
auto operator()(context ctx, Sender&& sndr) const //
-> std::optional<sync_wait_result_t<Sender>>
{
Expand Down
2 changes: 2 additions & 0 deletions include/stdexec/__detail/__env.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ namespace STDEXEC
sizeof...(_Envs) - __mcall<__mfind_if<__q1<__has_query_t>, __msize>, _Envs...>::value;
if constexpr (__index < sizeof...(_Envs))
return STDEXEC::__get<__index>(__env);
else
return void();
}
};
} // namespace __detail
Expand Down
29 changes: 15 additions & 14 deletions include/stdexec/__detail/__sender_concepts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ namespace STDEXEC
//! `sender_to<S, R>` is the strongest form of the sender concept: it
//! requires that @c S is a sender whose completion signatures can be
//! computed in @c R's environment, that @c R is a receiver that accepts
//! all of those signatures, *and* that @c connect(S, R) is well-formed.
//! all of those signatures, *and* that `connect(S, R)` is well-formed.
//!
//! This is the constraint a sender consumer or scheduler implementation
//! uses just before actually calling @c connect — it's the strongest
Expand Down Expand Up @@ -234,14 +234,20 @@ namespace STDEXEC
template <class _Sender>
concept dependent_sender = sender<_Sender> && __is_dependent_sender<_Sender>;

template <class _Sender, class... _Env>
using __single_sender_value_t = __value_types_t<__completion_signatures_of_t<_Sender, _Env...>,
__qq<__msingle>,
__qq<__msingle>>;
struct __as_single_value
{
template <class... _Ts>
requires(sizeof...(_Ts) >= 1)
using __f =
__mcall<__if_c<(sizeof...(_Ts) == 1), __q<__decay_t>, __qq<__decayed_std_tuple>>, _Ts...>;
};

//! See @c single-sender-value-type in [exec.snd.concepts] in the C++26 working draft.
template <class _Sender, class... _Env>
using __single_value_variant_sender_t =
__value_types_t<__completion_signatures_of_t<_Sender, _Env...>, __qq<__mlist>, __qq<__msingle>>;
using __single_sender_value_t = //
__value_types_t<__completion_signatures_of_t<_Sender, _Env...>,
__as_single_value,
__qq<__msingle>>;

template <class _Tag, class _Sender, class... _Env>
concept __sends = sender_in<_Sender, _Env...> //
Expand All @@ -255,23 +261,18 @@ namespace STDEXEC
using __never_sends_t = __mbool<__never_sends<_Tag, _Sender, _Env...>>;

template <class _Error>
using __is_eptr = __mbool<__decays_to<_Error, std::exception_ptr>>;
using __is_eptr_t = __mbool<__decays_to<_Error, std::exception_ptr>>;

template <class _Sender, class... _Env>
concept __has_eptr_completion = sender_in<_Sender, _Env...> //
&& __error_types_t<__completion_signatures_of_t<_Sender, _Env...>,
__q1<__is_eptr>,
__q1<__is_eptr_t>,
__qq<__mor_t>>::value;

template <class _Sender, class... _Env>
concept __single_value_sender = sender_in<_Sender, _Env...> //
&& requires { typename __single_sender_value_t<_Sender, _Env...>; };

template <class _Sender, class... _Env>
concept __single_value_variant_sender =
sender_in<_Sender, _Env...> //
&& requires { typename __single_value_variant_sender_t<_Sender, _Env...>; };

namespace __detail
{
template <class _SenderName, class _Sender, class... _Env>
Expand Down
66 changes: 32 additions & 34 deletions include/stdexec/__detail/__stopped_as_optional.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
#include "__sender_adaptor_closure.hpp"
#include "__senders.hpp"
#include "__transform_completion_signatures.hpp"
#include "__type_traits.hpp"

#include <exception>
#include <optional>
Expand All @@ -40,7 +39,7 @@ namespace STDEXEC
// [exec.stopped.opt]
namespace __sao
{
struct _SENDER_MUST_HAVE_EXACTLY_ONE_VALUE_COMPLETION_WITH_ONE_ARGUMENT_;
struct _SENDER_MUST_HAVE_EXACTLY_ONE_VALUE_COMPLETION_WITH_AT_LEAST_ONE_ARGUMENT_;

template <class _Receiver, class _Value>
struct __state
Expand All @@ -53,53 +52,48 @@ namespace STDEXEC

struct __stopped_as_optional_impl : __sexpr_defaults
{
template <class... _Tys>
requires(sizeof...(_Tys) == 1)
using __set_value_t = completion_signatures<set_value_t(std::optional<__decay_t<_Tys>>...)>;

template <class _Ty>
using __set_error_t = completion_signatures<set_error_t(_Ty)>;

template <class _Sender, class _Receiver>
using __value_type_t =
__decay_t<__single_sender_value_t<__child_of<_Sender>, env_of_t<_Receiver>>>;
static consteval auto __get_value_transform_fn() noexcept
{
return []<class... _Ts>()
{
using __value_t = __mcall<__as_single_value, _Ts...>;
return STDEXEC::__concat_completion_signatures(
completion_signatures<set_value_t(std::optional<__value_t>)>(),
__eptr_completion_unless_t<__nothrow_decay_copyable_t<_Ts...>>());
};
}

template <class _Self, class... _Env>
static constexpr auto __get_completion_signatures()
static consteval auto __get_completion_signatures()
{
static_assert(__sender_for<_Self, stopped_as_optional_t>);
auto __completions = STDEXEC::get_completion_signatures<__child_of<_Self>, _Env...>();

using __cv_sndr_t = __child_of<_Self>;
auto __completions = STDEXEC::get_completion_signatures<__cv_sndr_t, _Env...>();
STDEXEC_IF_OK(__completions)
{
using _Completions = decltype(__completions);
if constexpr (__single_value_sender<__child_of<_Self>, _Env...>)
if constexpr (!__single_value_sender<__cv_sndr_t, _Env...>)
{
return __transform_completion_signatures_t<
_Completions,
completion_signatures<set_error_t(std::exception_ptr)>,
__set_value_t,
__set_error_t,
completion_signatures<>>();
return STDEXEC::__throw_compile_time_error<
_WHAT_(_SENDER_MUST_HAVE_EXACTLY_ONE_VALUE_COMPLETION_WITH_AT_LEAST_ONE_ARGUMENT_),
_WHERE_(_IN_ALGORITHM_, stopped_as_optional_t),
_WITH_PRETTY_SENDER_<__cv_sndr_t>>();
}
else
{
return STDEXEC::__throw_compile_time_error<
_WHAT_(_SENDER_MUST_HAVE_EXACTLY_ONE_VALUE_COMPLETION_WITH_ONE_ARGUMENT_),
_WHERE_(_IN_ALGORITHM_, stopped_as_optional_t),
_WITH_PRETTY_SENDER_<__child_of<_Self>>>();
return STDEXEC::__transform_completion_signatures(__completions,
__get_value_transform_fn(),
{},
__ignore_completion());
}
}
};
}

static constexpr auto __get_state =
[]<class _Self, class _Receiver>(_Self&&, _Receiver&& __rcvr) noexcept
-> __state<_Receiver, __value_type_t<_Self, _Receiver>>
requires sender_in<__child_of<_Self>, env_of_t<_Receiver>>
-> __state<_Receiver, __single_sender_value_t<__child_of<_Self>, env_of_t<_Receiver>>>
{
static_assert(__sender_for<_Self, stopped_as_optional_t>);
using __value_t = __value_type_t<_Self, _Receiver>;
return __state<_Receiver, __value_t>{static_cast<_Receiver&&>(__rcvr)};
return {static_cast<_Receiver&&>(__rcvr)};
};

static constexpr auto __complete =
Expand All @@ -115,11 +109,15 @@ namespace STDEXEC
{
static_assert(__std::constructible_from<__value_t, _Args...>);
STDEXEC::set_value(static_cast<_State&&>(__state).__rcvr_,
std::optional<__value_t>{static_cast<_Args&&>(__args)...});
std::optional<__value_t>{std::in_place,
static_cast<_Args&&>(__args)...});
}
STDEXEC_CATCH_ALL
{
STDEXEC::set_error(static_cast<_State&&>(__state).__rcvr_, std::current_exception());
if constexpr (!__nothrow_decay_copyable<_Args...>)
{
STDEXEC::set_error(static_cast<_State&&>(__state).__rcvr_, std::current_exception());
}
}
}
else if constexpr (__same_as<_Tag, set_error_t>)
Expand Down
2 changes: 1 addition & 1 deletion include/stdexec/__detail/__sync_wait.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ namespace STDEXEC::__sync_wait

template <class _CvSender>
concept __valid_sync_wait_argument = __ok<__minvoke<
__mtry_catch_q<__single_value_variant_sender_t, __q<__too_many_successful_completions_error_t>>,
__mtry_catch_q<__single_sender_value_t, __q<__too_many_successful_completions_error_t>>,
_CvSender,
__env>>;
} // namespace STDEXEC::__sync_wait
Expand Down
6 changes: 5 additions & 1 deletion test/nvexec/common.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@
#include <cstdio>
#include <cstdlib>

STDEXEC_PRAGMA_PUSH()
STDEXEC_PRAGMA_IGNORE_EDG(is_constant_evaluated_in_nonconstexpr_context)

namespace
{

template <int N = 1>
requires(N > 0)
class flags_storage_t
Expand Down Expand Up @@ -362,3 +364,5 @@ namespace

static_assert(!std::is_trivially_copyable_v<move_only_t>);
} // namespace

STDEXEC_PRAGMA_POP()
22 changes: 12 additions & 10 deletions test/stdexec/algos/adaptors/test_stopped_as_optional.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,14 @@ namespace
wait_for_value(std::move(snd), std::optional<int>{11});
}

// TEST_CASE(
// "stopped_as_optional shall not work with multi-value senders",
// "[adaptors][stopped_as_optional]") {
// auto snd = ex::just(3, 0.1415) | ex::stopped_as_optional();
// static_assert(!ex::sender_to<decltype(snd), expect_error_receiver<>>);
// }
TEST_CASE("stopped_as_optional works with multi-value senders", "[adaptors][stopped_as_optional]")
{
auto snd = ex::just(3, 0.1415) | ex::stopped_as_optional();
wait_for_value(std::move(snd),
std::optional<std::tuple<int, double>>{
std::tuple{3, 0.1415}
});
}

TEST_CASE("stopped_as_optional shall not work with senders that have multiple alternatives",
"[adaptors][stopped_as_optional]")
Expand Down Expand Up @@ -111,13 +113,13 @@ namespace
error_scheduler sched2{};
error_scheduler<int> sched3{-1};

check_err_types<ex::__mset<std::exception_ptr>>(ex::just(11) | ex::continues_on(sched1)
| ex::stopped_as_optional());
check_err_types<ex::__mset<>>(ex::just(11) | ex::continues_on(sched1)
| ex::stopped_as_optional());
check_err_types<ex::__mset<std::exception_ptr>>(ex::just(13) | ex::continues_on(sched2)
| ex::stopped_as_optional());

check_err_types<ex::__mset<std::exception_ptr, int>>(ex::just(13) | ex::continues_on(sched3)
| ex::stopped_as_optional());
check_err_types<ex::__mset<int>>(ex::just(13) | ex::continues_on(sched3)
| ex::stopped_as_optional());
}

TEST_CASE("stopped_as_optional overrides sends_stopped to false",
Expand Down
4 changes: 3 additions & 1 deletion test/test_common/catch2.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Lucian Radu Teodorescu
* Copyright (c) 2026 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
Expand All @@ -20,6 +20,8 @@

STDEXEC_PRAGMA_PUSH()
STDEXEC_PRAGMA_IGNORE_GNU("-Wunused-parameter")
STDEXEC_PRAGMA_IGNORE_EDG(deprecated_entity)
STDEXEC_PRAGMA_IGNORE_EDG(deprecated_entity_with_custom_message)

#include <catch2/catch_all.hpp>

Expand Down
28 changes: 14 additions & 14 deletions test/test_common/receivers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ namespace
{
struct recv0
{
using receiver_concept = STDEXEC::receiver_tag;
using receiver_concept = ex::receiver_tag;

void set_value() noexcept {}

Expand All @@ -46,7 +46,7 @@ namespace

struct recv_int
{
using receiver_concept = STDEXEC::receiver_tag;
using receiver_concept = ex::receiver_tag;

void set_value(int) noexcept {}

Expand All @@ -57,7 +57,7 @@ namespace

struct recv0_ec
{
using receiver_concept = STDEXEC::receiver_tag;
using receiver_concept = ex::receiver_tag;

void set_value() noexcept {}

Expand All @@ -70,7 +70,7 @@ namespace

struct recv_int_ec
{
using receiver_concept = STDEXEC::receiver_tag;
using receiver_concept = ex::receiver_tag;

void set_value(int) noexcept {}

Expand All @@ -90,7 +90,7 @@ namespace
_Env env_{};

public:
using receiver_concept = STDEXEC::receiver_tag;
using receiver_concept = ex::receiver_tag;
base_expect_receiver() = default;

~base_expect_receiver()
Expand Down Expand Up @@ -153,7 +153,7 @@ namespace

struct expect_void_receiver_ex
{
using receiver_concept = STDEXEC::receiver_tag;
using receiver_concept = ex::receiver_tag;

expect_void_receiver_ex(bool& executed)
: executed_(&executed)
Expand Down Expand Up @@ -228,7 +228,7 @@ namespace
Env env_{};

public:
using receiver_concept = STDEXEC::receiver_tag;
using receiver_concept = ex::receiver_tag;

explicit expect_value_receiver_ex(T& dest)
: dest_(&dest)
Expand Down Expand Up @@ -295,7 +295,7 @@ namespace
template <class Env = ex::env<>>
struct expect_stopped_receiver_ex
{
using receiver_concept = STDEXEC::receiver_tag;
using receiver_concept = ex::receiver_tag;

explicit expect_stopped_receiver_ex(bool& executed)
: executed_(&executed)
Expand Down Expand Up @@ -415,7 +415,7 @@ namespace
template <class T, class Env = ex::env<>>
struct expect_error_receiver_ex
{
using receiver_concept = STDEXEC::receiver_tag;
using receiver_concept = ex::receiver_tag;

explicit expect_error_receiver_ex(T& value)
: value_(&value)
Expand Down Expand Up @@ -460,7 +460,7 @@ namespace

struct logging_receiver
{
using receiver_concept = STDEXEC::receiver_tag;
using receiver_concept = ex::receiver_tag;

logging_receiver(int& state)
: state_(&state)
Expand Down Expand Up @@ -499,7 +499,7 @@ namespace
template <class T>
struct typecat_receiver
{
using receiver_concept = STDEXEC::receiver_tag;
using receiver_concept = ex::receiver_tag;
T* value_;
typecat* cat_;

Expand Down Expand Up @@ -539,7 +539,7 @@ namespace
template <class F>
struct fun_receiver
{
using receiver_concept = STDEXEC::receiver_tag;
using receiver_concept = ex::receiver_tag;
F f_;

template <class... Ts>
Expand Down Expand Up @@ -590,10 +590,10 @@ namespace
{
// Ensure that the given sender type has only one variant for set_value calls
// If not, sync_wait will not work
static_assert(STDEXEC::__single_value_variant_sender<S, ex::__sync_wait::__env>,
static_assert(ex::__count_of<ex::set_value_t, S, ex::__sync_wait::__env>::value == 1,
"Sender passed to sync_wait needs to have one variant for sending set_value");

std::optional<std::tuple<Ts...>> res = STDEXEC::sync_wait(static_cast<S&&>(snd));
std::optional<std::tuple<Ts...>> res = ex::sync_wait(static_cast<S&&>(snd));
CHECK(res.has_value());
std::tuple<Ts...> expected(static_cast<Ts&&>(val)...);
if constexpr (std::tuple_size_v<std::tuple<Ts...>> == 1)
Expand Down
Loading