From 738d946a2d1003553d2ad1fa0e6475486a0bd1ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Neboj=C5=A1a=20Cvetkovi=C4=87?= Date: Wed, 21 Oct 2026 17:13:57 +0100 Subject: [PATCH] fix(stdexec): allocate a task's frame with allocator_type() when no allocator is passed [task.promise] specifies the promise's operator new(size_t) as operator new(size, allocator_arg, allocator_type()), both before and after P3980R1. The implementation passed std::allocator instead, so an environment's allocator_type was only used for the frame when the coroutine took an allocator_arg parameter. --- include/stdexec/__detail/__task.hpp | 2 +- test/stdexec/types/test_task.cpp | 49 +++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/include/stdexec/__detail/__task.hpp b/include/stdexec/__detail/__task.hpp index d2b8d5a59..8f1e62913 100644 --- a/include/stdexec/__detail/__task.hpp +++ b/include/stdexec/__detail/__task.hpp @@ -908,7 +908,7 @@ namespace STDEXEC // When no allocator passed to the coroutine: static void* operator new(size_t __bytes) { - return __promise::operator new(__bytes, std::allocator_arg, std::allocator{}); + return __promise::operator new(__bytes, std::allocator_arg, allocator_type()); } static void operator delete(void* __ptr, size_t __bytes) noexcept diff --git a/test/stdexec/types/test_task.cpp b/test/stdexec/types/test_task.cpp index 6819bcf3a..20685ca2d 100644 --- a/test/stdexec/types/test_task.cpp +++ b/test/stdexec/types/test_task.cpp @@ -129,6 +129,55 @@ namespace CHECK(bytes == 0); } + constinit std::size_t g_default_allocator_bytes = 0; + + template + struct default_counting_allocator + { + using value_type = T; + + default_counting_allocator() = default; + + template + default_counting_allocator(default_counting_allocator const &) noexcept + {} + + auto allocate(std::size_t n) -> T* + { + g_default_allocator_bytes += n * sizeof(T); + return static_cast(::operator new(n * sizeof(T))); + } + + void deallocate(T* p, std::size_t n) + { + g_default_allocator_bytes -= n * sizeof(T); + ::operator delete(p); + } + + auto operator==(default_counting_allocator const &) const -> bool = default; + }; + + struct default_allocator_env : ex::env<> + { + using allocator_type = default_counting_allocator; + }; + + auto test_task_default_allocator() -> ex::task + { + co_return; + } + + TEST_CASE("task without an allocator argument allocates its frame with a default-constructed " + "allocator_type", + "[types][task]") + { + g_default_allocator_bytes = 0; + auto t = test_task_default_allocator(); + CHECK(g_default_allocator_bytes > 0); + ex::sync_wait(std::move(t)); + CHECK(g_default_allocator_bytes == 0); + } + auto test_task_awaits_just_sender() -> ex::task { co_return co_await ex::just(42);