NVIDIA/stdexec

`exec::finally()` shouldn't include `std::exception_ptr` in the error types if neither the source or final senders include that error type

lewissbaker opened this issue · 3 comments

I would have expected the following code to compile, but it fails because let_error tries to instantiate a call to the lambda with an argument of type std::exception_ptr.

https://godbolt.org/z/PK8YjohW8

#include <stdexec/execution.hpp>
#include <exec/finally.hpp>

#include <cstdio>

struct X {
    X() : value(0) { std::puts("X::X()"); }
    X(X&& o) noexcept : value(o.value) { std::puts("X::X(X&&)"); }
    X(const X& o) noexcept : value(o.value)  { std::puts("X::X(const X&)"); }
    ~X() { std::puts("X::~X()"); value = -1; }
    int value;
};

int main() {
    auto result = stdexec::sync_wait(
        stdexec::let_error(
            exec::finally(
                stdexec::just_error(X{}),
                stdexec::just()),
            [](const X& x) noexcept {
                std::printf("caught X(value=%i)\n", x.value);
                return stdexec::just();
            }));
}

I suspect that this is because we don't know ahead of time whether connect() is potentially throwing.
We could potentially work around this by instead connecting the final-sender at connect-time of the finally sender. This way, we know that we will be able start the final-operation when the input operation completes regardless of whether the connect() method is throwing or not.

We could potentially work around this by instead connecting the final-sender at connect-time of the finally sender. This way, we know that we will be able start the final-operation when the input operation completes regardless of whether the connect() method is throwing or not.

That has trade-offs. We would no longer be able to have the initial op and the final op share space in the finally operation state.

That has trade-offs. We would no longer be able to have the initial op and the final op share space in the finally operation state.

Maybe we could still have them share space if the connect() method on the final-sender was noexcept?

I think this line should be reverted. Otherwise, the final-sender will lose its states.
I don't know whether the final-sender should be reset by design?

@@ -236,7 +236,7 @@ namespace exec {
         this->__result_.__construct(
           std::in_place_type<__decayed_tuple<_Args...>>, static_cast<_Args&&>(__args)...);
         STDEXEC_ASSERT(__op_.index() == 0);
-        _FinalSender& __final = std::get_if<0>(&__op_)->__sndr_;
+        _FinalSender __final = static_cast<_FinalSender&&>(std::get_if<0>(&__op_)->__sndr_);
         __final_op_t& __final_op = __op_.template emplace<1>(__conv{[&] {
           return stdexec::connect(static_cast<_FinalSender&&>(__final), __final_receiver_t{this});
         }});