Skip to content

starts_on: a child reading get_stop_token from the environment fails to compile with clang (gcc accepts it) #2240

Description

@kyusic

I spawn a child into a counting_scope. The child reads the stop token out of the
environment and passes it to a coroutine that takes stdexec::inplace_stop_token. The
scope supplies a real inplace_stop_token, and the program runs as expected when it
compiles.

Wrapping that child in starts_on makes clang reject the program. gcc accepts the same
code.

#include <utility>

#include <stdexec/execution.hpp>

namespace ex = stdexec;

// In real code this is a member function defined in a .cpp, so its parameter
// type is fixed.
auto work(ex::inplace_stop_token token) -> ex::task<void> {
  (void)token;
  co_return;
}

int main() {
  ex::run_loop loop;
  ex::counting_scope scope;

  auto child = ex::let_value(ex::read_env(ex::get_stop_token),
                             [](auto token) -> ex::task<void> {
                               co_await work(token);
                             });

  ex::spawn(ex::starts_on(loop.get_scheduler(), std::move(child)) |
                ex::upon_error([](auto&&) noexcept {}),
            scope.get_token());
  ex::sync_wait(scope.join());
}
clang++ -std=c++26 -Iinclude -fsyntax-only repro.cpp

fails with:

repro.cpp:20:41: error: no matching function for call to 'work'
   20 |                                co_await work(token);
      |                                         ^~~~
include/stdexec/__detail/../__detail/../functional.hpp:168:16: note: in instantiation of function template specialization 'main()::(anonymous
      class)::operator()<stdexec::never_stop_token>' requested here
  168 |         return static_cast<_Fun &&>(__fun)(static_cast<_Args &&>(__args)...);
      |                ^
...
repro.cpp:9:6: note: candidate function not viable: no known conversion from 'stdexec::never_stop_token' to 'ex::inplace_stop_token' for 1st argument
    9 | auto work(ex::inplace_stop_token token) -> ex::task<void> {
      |      ^    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.

The same command with g++ -std=c++26 compiles.

Variants, each changing one thing in the program above:

variant g++ 15.2.0 clang 21.1.8
as written compiles fails
work takes the token as auto instead of inplace_stop_token compiles compiles
the child does not read the environment compiles compiles
the scheduler is given through write_env(prop{get_start_scheduler, sched}) instead of starts_on compiles compiles
sync_wait with the token in the environment, instead of spawning into a scope compiles compiles
connect to a receiver whose environment carries the token, instead of spawning into a scope compiles compiles

Is the child expected to be instantiated with an environment that has no stop token here,
or am I holding starts_on wrong?

Versions:

  • stdexec main @ f91f6363 (2026-08-22)
  • g++ 15.2.0, clang 21.1.8, both -std=c++26
  • clang 22.1.2 rejects the program the same way

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions