diff --git a/examples/external/callback_awaitable.hpp b/examples/external/callback_awaitable.hpp index fdd421d..0c62323 100644 --- a/examples/external/callback_awaitable.hpp +++ b/examples/external/callback_awaitable.hpp @@ -41,7 +41,7 @@ template struct callback_awaitable_impl { tmc::detail::result_storage_t result; friend Awaitable; - callback_awaitable_impl(Awaitable& Handle) : handle(Handle) {} + callback_awaitable_impl(Awaitable& Handle TMC_LIFETIMEBOUND) : handle(Handle) {} bool await_ready() { return false; } @@ -121,7 +121,7 @@ template struct wrapper { ); } - callback_awaitable_impl operator co_await() { + callback_awaitable_impl operator co_await() TMC_LIFETIMEBOUND { return callback_awaitable_impl(*this); } }; @@ -166,7 +166,7 @@ template struct awaitable_traits { using self_type = Awaitable; // Values controlling the behavior when awaited directly in a tmc::task - static decltype(auto) get_awaiter(self_type&& awaitable) { + static decltype(auto) get_awaiter(self_type&& awaitable TMC_LIFETIMEBOUND) { return std::forward(awaitable).operator co_await(); } diff --git a/examples/external/external_awaitable.cpp b/examples/external/external_awaitable.cpp index 7f70210..c6b6e41 100644 --- a/examples/external/external_awaitable.cpp +++ b/examples/external/external_awaitable.cpp @@ -54,7 +54,7 @@ template class external_awaitable { }); } - Result& await_resume() & noexcept { return result; } + Result& await_resume() & noexcept TMC_LIFETIMEBOUND { return result; } Result&& await_resume() && noexcept { return std::move(result); } ~external_awaitable() { diff --git a/examples/external/external_coro.hpp b/examples/external/external_coro.hpp index 0a4b876..f056fc2 100644 --- a/examples/external/external_coro.hpp +++ b/examples/external/external_coro.hpp @@ -4,6 +4,17 @@ #include #include +// This example type deliberately has no knowledge of TMC, so it defines its +// own equivalent of TMC_LIFETIMEBOUND rather than using tmc/detail/compat.hpp. +#if defined(__has_cpp_attribute) +#if __has_cpp_attribute(clang::lifetimebound) +#define EXTERNAL_CORO_LIFETIMEBOUND [[clang::lifetimebound]] +#endif +#endif +#ifndef EXTERNAL_CORO_LIFETIMEBOUND +#define EXTERNAL_CORO_LIFETIMEBOUND +#endif + // A simple "external" awaitable coroutine type that has no knowledge of TMC. template class aw_external_coro; template struct external_coro_promise; @@ -80,7 +91,7 @@ template class aw_external_coro { p.result_ptr = &result; return handle; } - inline Result& await_resume() & noexcept { + inline Result& await_resume() & noexcept EXTERNAL_CORO_LIFETIMEBOUND { handle.destroy(); return result; } diff --git a/examples/external/external_executor.cpp b/examples/external/external_executor.cpp index 7c4b739..7981eda 100644 --- a/examples/external/external_executor.cpp +++ b/examples/external/external_executor.cpp @@ -86,7 +86,7 @@ class tmc_external_executor { /// This object shares a lifetime with this executor, and can be used for /// pointer-based equality comparison against /// the thread-local `tmc::current_executor()`. - tmc::ex_any* type_erased() { return &type_erased_this; } + tmc::ex_any* type_erased() TMC_LIFETIMEBOUND { return &type_erased_this; } }; // A complete, minimal implementation of executor_traits. @@ -105,7 +105,7 @@ template <> struct tmc::detail::executor_traits { Ex.post_bulk(std::forward(Items), Count, Priority, ThreadHint); } - static inline tmc::ex_any* type_erased(tmc_external_executor& Ex) { + static inline tmc::ex_any* type_erased(tmc_external_executor& Ex TMC_LIFETIMEBOUND) { return Ex.type_erased(); } diff --git a/examples/pipeline_fifo.hpp b/examples/pipeline_fifo.hpp index b681284..55fd715 100644 --- a/examples/pipeline_fifo.hpp +++ b/examples/pipeline_fifo.hpp @@ -43,11 +43,11 @@ template class with_result_of_t { public: using T = decltype(std::declval()()); - explicit with_result_of_t(F&& f) : fun(std::forward(f)) {} + explicit with_result_of_t(F&& f TMC_LIFETIMEBOUND) : fun(std::forward(f)) {} operator T() noexcept { return fun(); } }; -template inline with_result_of_t with_result_of(F&& f) { +template inline with_result_of_t with_result_of(F&& f TMC_LIFETIMEBOUND) { return with_result_of_t(std::forward(f)); } diff --git a/tests/atomic_awaitable.hpp b/tests/atomic_awaitable.hpp index 990b464..3cea703 100644 --- a/tests/atomic_awaitable.hpp +++ b/tests/atomic_awaitable.hpp @@ -38,7 +38,7 @@ template struct atomic_awaitable : private AtomicAwaitableTag { } std::atomic& ref() { return value; } - operator std::atomic&() { return value; } + operator std::atomic&() TMC_LIFETIMEBOUND { return value; } T load() { return value.load(); } void async_initiate() { @@ -84,7 +84,9 @@ template struct awaitable_traits { using self_type = Awaitable; // Values controlling the behavior when awaited directly in a tmc::task - static decltype(auto) get_awaiter(self_type& awaitable) noexcept { return awaitable; } + static decltype(auto) get_awaiter(self_type& awaitable TMC_LIFETIMEBOUND) noexcept { + return awaitable; + } // Values controlling the behavior when wrapped by a utility function // such as tmc::spawn_*() diff --git a/tests/test_channel.cpp b/tests/test_channel.cpp index d67c7c4..899f9fb 100644 --- a/tests/test_channel.cpp +++ b/tests/test_channel.cpp @@ -417,7 +417,8 @@ struct move_counter { int value; std::atomic* count; - move_counter(int v, std::atomic* c) noexcept : value(v), count(c) {} + move_counter(int v, std::atomic* c TMC_LIFETIMEBOUND) noexcept + : value(v), count(c) {} move_counter(move_counter&& Other) noexcept : value(Other.value), count(Other.count) { Other.count = nullptr; @@ -541,7 +542,9 @@ struct immovable_destructor_counter { size_t value; std::atomic* count; - immovable_destructor_counter(size_t v, std::atomic* c) noexcept + immovable_destructor_counter( + size_t v, std::atomic* c TMC_LIFETIMEBOUND + ) noexcept : value(v), count(c) {} ~immovable_destructor_counter() { ++(*count); } diff --git a/tests/test_chase_lev_deque.cpp b/tests/test_chase_lev_deque.cpp index f53a402..fe66d4a 100644 --- a/tests/test_chase_lev_deque.cpp +++ b/tests/test_chase_lev_deque.cpp @@ -358,7 +358,12 @@ TEST_F(CATEGORY, concurrent_owner_and_stealers) { stealers.reserve(NUM_STEALERS); for (size_t s = 0; s < NUM_STEALERS; ++s) { stealers.emplace_back([&, s]() { + // The invalidation warning is a false positive: push_back on the inner + // vector cannot invalidate `out`, which refers to the inner vector + // object itself (not to its elements). + TMC_DISABLE_WARNING_LIFETIME_INVALIDATION_BEGIN auto& out = stolen[static_cast(s)]; + TMC_DISABLE_WARNING_LIFETIME_INVALIDATION_END out.reserve(N / NUM_STEALERS); size_t v = 0; while (true) { @@ -446,7 +451,12 @@ TEST_F(CATEGORY, concurrent_post_bulk_with_stealers) { stealers.reserve(NUM_STEALERS); for (size_t s = 0; s < NUM_STEALERS; ++s) { stealers.emplace_back([&, s]() { + // The invalidation warning is a false positive: push_back on the inner + // vector cannot invalidate `out`, which refers to the inner vector + // object itself (not to its elements). + TMC_DISABLE_WARNING_LIFETIME_INVALIDATION_BEGIN auto& out = stolen[static_cast(s)]; + TMC_DISABLE_WARNING_LIFETIME_INVALIDATION_END size_t v = 0; while (true) { if (q.steal(v)) { diff --git a/tests/test_common.hpp b/tests/test_common.hpp index f2bce41..15c3e5d 100644 --- a/tests/test_common.hpp +++ b/tests/test_common.hpp @@ -44,7 +44,7 @@ template tmc::task inc_task_int(Arr& arr, size_t idx) { struct destructor_counter { std::atomic* count; - destructor_counter(std::atomic* C) noexcept : count{C} {} + destructor_counter(std::atomic* C TMC_LIFETIMEBOUND) noexcept : count{C} {} destructor_counter(destructor_counter const& Other) = delete; destructor_counter& operator=(destructor_counter const& Other) = delete; diff --git a/tests/test_coro_functor.cpp b/tests/test_coro_functor.cpp index ba515fe..01f0d13 100644 --- a/tests/test_coro_functor.cpp +++ b/tests/test_coro_functor.cpp @@ -52,7 +52,7 @@ TEST_F(CATEGORY, lambda) { struct inv { int* r; int* d; - inv(int& R, int& D) : r(&R), d(&D) {} + inv(int& R TMC_LIFETIMEBOUND, int& D TMC_LIFETIMEBOUND) : r(&R), d(&D) {} void operator()() { EXPECT_NE(r, nullptr); *r = 1; diff --git a/tests/test_exceptions.cpp b/tests/test_exceptions.cpp index 7d639a3..15df648 100644 --- a/tests/test_exceptions.cpp +++ b/tests/test_exceptions.cpp @@ -401,7 +401,8 @@ TEST_F(CATEGORY, wrapper_throw_move_only_unknown) { template struct aw_throw_on_other_executor_void : public KnownTag { tmc::ex_braid& other_ex; - aw_throw_on_other_executor_void(tmc::ex_braid& OtherEx) : other_ex{OtherEx} {} + aw_throw_on_other_executor_void(tmc::ex_braid& OtherEx TMC_LIFETIMEBOUND) + : other_ex{OtherEx} {} bool await_ready() { return false; } void await_suspend(std::coroutine_handle<> Outer) { tmc::detail::post_checked(other_ex.type_erased(), std::move(Outer)); @@ -412,7 +413,8 @@ struct aw_throw_on_other_executor_void : public KnownTag { template struct aw_throw_on_other_executor_int : public KnownTag { tmc::ex_braid& other_ex; - aw_throw_on_other_executor_int(tmc::ex_braid& OtherEx) : other_ex{OtherEx} {} + aw_throw_on_other_executor_int(tmc::ex_braid& OtherEx TMC_LIFETIMEBOUND) + : other_ex{OtherEx} {} bool await_ready() { return false; } void await_suspend(std::coroutine_handle<> Outer) { tmc::detail::post_checked(other_ex.type_erased(), std::move(Outer)); diff --git a/tests/test_mux_many.ipp b/tests/test_mux_many.ipp index 217d40d..c64a9ac 100644 --- a/tests/test_mux_many.ipp +++ b/tests/test_mux_many.ipp @@ -310,7 +310,7 @@ TEST_F(CATEGORY, mux_many_eager_async_initiate_unknown_sized_iterator) { int i; using value_type = mux_immediate_async_op; value_type operator*() const { return mux_immediate_async_op(1 << i); } - op_iter& operator++() { + op_iter& operator++() TMC_LIFETIMEBOUND { ++i; return *this; } diff --git a/tests/test_mux_tuple_zerocopy.cpp b/tests/test_mux_tuple_zerocopy.cpp index f9d80a1..21e9c65 100644 --- a/tests/test_mux_tuple_zerocopy.cpp +++ b/tests/test_mux_tuple_zerocopy.cpp @@ -67,7 +67,8 @@ struct zc_config : tmc::qu_mpsc_unbounded_default_config { struct counted { std::atomic* count; size_t value; - counted(std::atomic* C, size_t V) noexcept : count{C}, value{V} {} + counted(std::atomic* C TMC_LIFETIMEBOUND, size_t V) noexcept + : count{C}, value{V} {} counted(counted const&) = delete; counted& operator=(counted const&) = delete; ~counted() { @@ -85,7 +86,8 @@ struct no_default_counted { std::atomic* count; size_t value; no_default_counted() = delete; - no_default_counted(std::atomic* C, size_t V) noexcept : count{C}, value{V} {} + no_default_counted(std::atomic* C TMC_LIFETIMEBOUND, size_t V) noexcept + : count{C}, value{V} {} no_default_counted(no_default_counted const&) = delete; no_default_counted& operator=(no_default_counted const&) = delete; no_default_counted(no_default_counted&& Other) noexcept diff --git a/tests/test_qu_mpsc_unbounded.cpp b/tests/test_qu_mpsc_unbounded.cpp index 4dbe670..230a6ae 100644 --- a/tests/test_qu_mpsc_unbounded.cpp +++ b/tests/test_qu_mpsc_unbounded.cpp @@ -33,7 +33,7 @@ template struct q_config : tmc::qu_mpsc_unbounded_default_config { struct mpsc_destructor_counter { std::atomic* count; [[maybe_unused]] mpsc_destructor_counter() noexcept : count{nullptr} {} - mpsc_destructor_counter(std::atomic* C) noexcept : count{C} {} + mpsc_destructor_counter(std::atomic* C TMC_LIFETIMEBOUND) noexcept : count{C} {} mpsc_destructor_counter(mpsc_destructor_counter const& Other) = delete; mpsc_destructor_counter& operator=(mpsc_destructor_counter const& Other) = delete; diff --git a/tests/test_qu_spsc_bounded.cpp b/tests/test_qu_spsc_bounded.cpp index 9e8e83a..f60286e 100644 --- a/tests/test_qu_spsc_bounded.cpp +++ b/tests/test_qu_spsc_bounded.cpp @@ -36,7 +36,7 @@ template struct qu_config : tmc::qu_spsc_bounded_default_config { struct spsc_destructor_counter { std::atomic* count; [[maybe_unused]] spsc_destructor_counter() noexcept : count{nullptr} {} - spsc_destructor_counter(std::atomic* C) noexcept : count{C} {} + spsc_destructor_counter(std::atomic* C TMC_LIFETIMEBOUND) noexcept : count{C} {} spsc_destructor_counter(spsc_destructor_counter const& Other) = delete; spsc_destructor_counter& operator=(spsc_destructor_counter const& Other) = delete; diff --git a/tests/test_qu_spsc_unbounded.cpp b/tests/test_qu_spsc_unbounded.cpp index 8e845fe..dfb6c54 100644 --- a/tests/test_qu_spsc_unbounded.cpp +++ b/tests/test_qu_spsc_unbounded.cpp @@ -34,7 +34,7 @@ template struct qu_config : tmc::qu_spsc_unbounded_default_config struct spsc_destructor_counter { std::atomic* count; [[maybe_unused]] spsc_destructor_counter() noexcept : count{nullptr} {} - spsc_destructor_counter(std::atomic* C) noexcept : count{C} {} + spsc_destructor_counter(std::atomic* C TMC_LIFETIMEBOUND) noexcept : count{C} {} spsc_destructor_counter(spsc_destructor_counter const& Other) = delete; spsc_destructor_counter& operator=(spsc_destructor_counter const& Other) = delete; diff --git a/tests/test_spawn_composition.ipp b/tests/test_spawn_composition.ipp index 5321307..8d8469b 100644 --- a/tests/test_spawn_composition.ipp +++ b/tests/test_spawn_composition.ipp @@ -284,7 +284,7 @@ static inline tmc::task spawn_many_compose_spawn_func_many() { return tmc::spawn_func_many<2>( ( std::ranges::views::iota(void_results.data() + (i * 2)) | - std::ranges::views::transform([](int* j) -> auto { + std::ranges::views::transform([](int* j TMC_LIFETIMEBOUND) -> auto { return [j]() { *j = (1 << *j); }; }) ).begin()