Skip to content

adds assert_with concept and uses it for get_completion_signatures an…#1815

Open
kirkshoop wants to merge 1 commit intoNVIDIA:mainfrom
kirkshoop:assert-with
Open

adds assert_with concept and uses it for get_completion_signatures an…#1815
kirkshoop wants to merge 1 commit intoNVIDIA:mainfrom
kirkshoop:assert-with

Conversation

@kirkshoop
Copy link
Contributor

I have tried quite hard to create a useful __mexception when things fail and then coerce those into the result types of functions like get_completion_signatures and connect and then to propagate them all the way through the layers.

The recent changes have had me discard three previous attempts to prevent the actual error from being hidden by SFINAE and by static_assert s that do not expand the problem sexpr into a form that is identifiable.

This PR proposes a different approach. I have described this approach several times. Here it is in code.

With this PR -

Misspelling the name of this connect:

constexpr auto conNnect(_ItemRcvr __rcvr) const & noexcept(__nothrow_decay_copyable<_ItemRcvr>)

Causes this test case:

TEST_CASE("iterate - sum up an array ", "[sequence_senders][iterate]") {

To encounter a compile error while evaluating this line deep inside of the iterate() sequence implementation:

__optional<connect_result_t<__next_sender_t, __next_receiver_t>> __op_{};

Which produces this log:

FAILED: [code=1] test/exec/CMakeFiles/test.exec.dir/sequence/test_iterate.cpp.o 
/usr/bin/c++ -DSTDEXEC_ENABLE_LIBDISPATCH -DSTDEXEC_NAMESPACE=std::execution -I[path]/stdexec/include -I[path]/stdexec/build-m1/include -I[path]/stdexec/build-m1/_deps/catch2-src/single_include -I[path]/stdexec/test -O3 -DNDEBUG -std=gnu++20 -arch arm64 -Wall -Werror=unused-parameter -ferror-limit=0 -fmacro-backtrace-limit=0 -ftemplate-backtrace-limit=0 -MD -MT test/exec/CMakeFiles/test.exec.dir/sequence/test_iterate.cpp.o -MF test/exec/CMakeFiles/test.exec.dir/sequence/test_iterate.cpp.o.d -o test/exec/CMakeFiles/test.exec.dir/sequence/test_iterate.cpp.o -c [path]/stdexec/test/exec/sequence/test_iterate.cpp
In file included from [path]/stdexec/test/exec/sequence/test_iterate.cpp:18:
In file included from [path]/stdexec/include/exec/sequence/iterate.hpp:24:
In file included from [path]/stdexec/include/exec/sequence/../../stdexec/execution.hpp:22:
In file included from [path]/stdexec/include/exec/sequence/../../stdexec/__detail/__as_awaitable.hpp:21:
In file included from [path]/stdexec/include/exec/sequence/../../stdexec/__detail/__completion_signatures_of.hpp:21:
In file included from [path]/stdexec/include/exec/sequence/../../stdexec/__detail/__debug.hpp:20:
In file included from [path]/stdexec/include/exec/sequence/../../stdexec/__detail/__completion_signatures.hpp:22:
[path]/stdexec/include/exec/sequence/../../stdexec/__detail/__diagnostics.hpp:280:19: 
error: static assertion failed: concept assertion failed with..
  280 |     static_assert(__ok<_WithError>, "concept assertion failed with..");
      |                   ^~~~~~~~~~~~~~~~

[path]/stdexec/include/exec/sequence/../../stdexec/__detail/__diagnostics.hpp:288:25: 
note: in instantiation of template 
  class 'std::execution::__assert_with<false, std::execution::_ERROR_<std::execution::_WHAT_ (std::execution::_CONNECT_ERROR_), std::execution::_WHY_ (std::execution::_UNABLE_TO_CONNECT_THE_SENDER_TO_THE_RECEIVER_), std::execution::_WITH_SENDER_<exec::__iterate::__sender<int *, int *>>, std::execution::_WITH_RECEIVER_ (exec::_seq::_rcvr<(anonymous namespace)::sum_item_rcvr<exec::__iterate::__next_receiver<int *, int *, (anonymous namespace)::sum_receiver<>>>, true>), std::execution::_WITH_ENVIRONMENT_ (std::execution::__env::env<>)>>' requested here
  288 |   concept assert_with = __assert_with<_MustBeTrue, _WithError>::value;

...

Notice that the connect that fails is far removed from the sequence's subscribe() and deeply buried inside the implementation of iterate().
Getting the failing connect to create an __mexception and then propagating that as the result of the subscribe() on the sequence returned from iterate() is really hard to do the first time and unlikely to continue to work after each future change to the implementation of iterate().

This also allows trailing return types to be specified without causing SFINAE - as long as the assert_with constraints in the requires clause cover all the failure cases for computing the return type.

NOTE: I removed a couple of tests because it appeared that they relied on connect() SFINAE and they were now compile errors.

@copy-pr-bot
Copy link

copy-pr-bot bot commented Feb 5, 2026

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@ericniebler
Copy link
Collaborator

ericniebler commented Feb 5, 2026

i am interested in this. i need to do some experimentation with it first, and i'm on a hard deadline so it could be a while.

but anyway...

Getting the failing connect to create an __mexception and then propagating that as the result

nowhere in stdexec does any connect customization return a __mexception type. that is only for get_completion_signatures, and even that should be replaced with calls to __throw_compile_time_error.

when i similarly misspell connect in iterate.hpp, i get a long template instantiation backtrace (bad) that ends with all the ways it tried to find a connect customization and the reasons they were not viable (very good!):

Truncated spew
/home/eniebler/Code/stdexec/include/exec/sequence/../../stdexec/__detail/__connect.hpp:211:22: note: candidate template ignored: constraints not satisfied [with _Sender = exec::__iterate::__sender<int *, int *>, _Receiver = exec::_seq::_rcvr<(anonymous
      namespace)::sum_item_rcvr<exec::__iterate::__next_receiver<int *, int *, (anonymous namespace)::sum_receiver<>>>, true>, _DeclFn = __connect_declfn_t<exec::__iterate::__sender<int *, int *>, exec::_seq::_rcvr<(anonymous namespace)::sum_item_rcvr<exec::__iterate::__next_receiver<int *, int *,
      (anonymous namespace)::sum_receiver<std::execution::__env::env<>>>>, true>>]
  211 |       constexpr auto operator()(_Sender&& __sndr, _Receiver&& __rcvr) const
      |                      ^
/home/eniebler/Code/stdexec/include/exec/sequence/../../stdexec/__detail/__connect.hpp:209:18: note: because '__connectable_to<exec::__iterate::__sender<int *, int *>, exec::_seq::_rcvr<(anonymous namespace)::sum_item_rcvr<exec::__iterate::__next_receiver<int *, int *,
      (anonymous namespace)::sum_receiver<>>>, true>>' evaluated to false
  209 |         requires __connectable_to<_Sender, _Receiver>
      |                  ^
/home/eniebler/Code/stdexec/include/exec/sequence/../../stdexec/__detail/__connect.hpp:57:58: note: because type constraint '__connect::__with_any_connect<exec::__iterate::__sender<int *, int *>, exec::_seq::_rcvr<(anonymous namespace)::sum_item_rcvr<exec::__iterate::__next_receiver<int *,
      int *, (anonymous namespace)::sum_receiver<>>>, true>>' was not satisfied:
   57 |     { transform_sender(__sndr(), get_env(__rcvr())) } -> __connect::__with_any_connect<_Receiver>;
      |                                                          ^
/home/eniebler/Code/stdexec/include/exec/sequence/../../stdexec/__detail/__connect.hpp:49:34: note: because '__with_static_member<exec::__iterate::__sender<int *, int *>, exec::_seq::_rcvr<(anonymous namespace)::sum_item_rcvr<exec::__iterate::__next_receiver<int *, int *,
      (anonymous namespace)::sum_receiver<>>>, true>>' evaluated to false
   49 |     concept __with_any_connect = __with_static_member<_Sender, _Receiver>
      |                                  ^
/home/eniebler/Code/stdexec/include/exec/sequence/../../stdexec/__detail/__connect.hpp:33:44: note: because 'std::execution::__unref_t<_Sender>::static_connect(__sndr(), __rcvr())' would be invalid: no member named 'static_connect' in 'exec::__iterate::__sender<int *, int *>'
   33 |         STDEXEC_REMOVE_REFERENCE(_Sender)::static_connect(__sndr(), __rcvr());
      |                                            ^
/home/eniebler/Code/stdexec/include/exec/sequence/../../stdexec/__detail/__connect.hpp:50:34: note: and '__with_member<exec::__iterate::__sender<int *, int *>, exec::_seq::_rcvr<(anonymous namespace)::sum_item_rcvr<exec::__iterate::__next_receiver<int *, int *,
      (anonymous namespace)::sum_receiver<>>>, true>>' evaluated to false
   50 |                               || __with_member<_Sender, _Receiver>
      |                                  ^
+/home/eniebler/Code/stdexec/include/exec/sequence/../../stdexec/__detail/__connect.hpp:39:18: note: because '__sndr().connect(__rcvr())' would be invalid: no member named 'connect' in 'exec::__iterate::__sender<int *, int *>'
+   39 |         __sndr().connect(__rcvr());
+      |                  ^
/home/eniebler/Code/stdexec/include/exec/sequence/../../stdexec/__detail/__connect.hpp:51:34: note: and '__with_co_await<exec::__iterate::__sender<int *, int *>, exec::_seq::_rcvr<(anonymous namespace)::sum_item_rcvr<exec::__iterate::__next_receiver<int *, int *,
      (anonymous namespace)::sum_receiver<>>>, true>>' evaluated to false
   51 |                               || __with_co_await<_Sender, _Receiver>
      |                                  ^
/home/eniebler/Code/stdexec/include/exec/sequence/../../stdexec/__detail/__connect.hpp:43:31: note: because '__awaitable<exec::__iterate::__sender<int *, int *>, __connect_await::__promise<exec::_seq::_rcvr<(anonymous namespace)::sum_item_rcvr<exec::__iterate::__next_receiver<int *, int *,
      (anonymous namespace)::sum_receiver<std::execution::__env::env<>>>>, true>>>' evaluated to false
   43 |     concept __with_co_await = __awaitable<_Sender, __connect_await::__promise<_Receiver>>;
      |                               ^
/home/eniebler/Code/stdexec/include/exec/sequence/../../stdexec/__detail/__awaitable.hpp:88:10: note: because type constraint '__awaiter<exec::__iterate::__sender<int *, int *> &&, std::execution::__connect_await::__promise<exec::_seq::_rcvr<(anonymous
      namespace)::sum_item_rcvr<exec::__iterate::__next_receiver<int *, int *, (anonymous namespace)::sum_receiver<>>>, true>>>' was not satisfied:
   88 |     } -> __awaiter<_Promise...>;
      |          ^
/home/eniebler/Code/stdexec/include/exec/sequence/../../stdexec/__detail/__awaitable.hpp:31:15: note: because '__awaiter.await_ready() ? 1 : 0' would be invalid: no member named 'await_ready' in 'exec::__iterate::__sender<int *, int *>'
   31 |     __awaiter.await_ready() ? 1 : 0;
      |               ^
/home/eniebler/Code/stdexec/include/exec/sequence/../../stdexec/__detail/__connect.hpp:52:34: note: and '__with_legacy_tag_invoke<exec::__iterate::__sender<int *, int *>, exec::_seq::_rcvr<(anonymous namespace)::sum_item_rcvr<exec::__iterate::__next_receiver<int *, int *,
      (anonymous namespace)::sum_receiver<>>>, true>>' evaluated to false
   52 |                               || __with_legacy_tag_invoke<_Sender, _Receiver>;
      |                                  ^
/home/eniebler/Code/stdexec/include/exec/sequence/../../stdexec/__detail/__connect.hpp:46:40: note: because '__tag_invocable<connect_t, exec::__iterate::__sender<int *, int *>, exec::_seq::_rcvr<(anonymous namespace)::sum_item_rcvr<exec::__iterate::__next_receiver<int *, int *,
      (anonymous namespace)::sum_receiver<>>>, true>>' evaluated to false
   46 |     concept __with_legacy_tag_invoke = __tag_invocable<connect_t, _Sender, _Receiver>;
      |                                        ^
/home/eniebler/Code/stdexec/include/exec/sequence/../../stdexec/__detail/__tag_invoke.hpp:32:7: note: because 'tag_invoke(static_cast<_Tag &&>(__tag), static_cast<_Args &&>(__args)...)' would be invalid: no matching function for call to 'tag_invoke'
   32 |       tag_invoke(static_cast<_Tag&&>(__tag), static_cast<_Args&&>(__args)...);
      |       ^
1 error generated.

that is something i very much want to preserve, whatever we end up doing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants