From 2c2bbad854fb7bca0e247ee73c8cc396911d8164 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sun, 10 May 2026 19:10:09 -0700 Subject: [PATCH 1/3] Assume the presence of c++20 ranges --- include/stdexec/__detail/__ranges.hpp | 98 +-------------------------- 1 file changed, 3 insertions(+), 95 deletions(-) diff --git a/include/stdexec/__detail/__ranges.hpp b/include/stdexec/__detail/__ranges.hpp index 71e738a63..fdbe5a2ec 100644 --- a/include/stdexec/__detail/__ranges.hpp +++ b/include/stdexec/__detail/__ranges.hpp @@ -17,11 +17,9 @@ #include "__config.hpp" -#if !STDEXEC_NO_STDCPP_RANGES() +#include -# include - -# include "__prologue.hpp" +#include "__prologue.hpp" namespace STDEXEC::ranges { @@ -34,94 +32,4 @@ namespace STDEXEC::ranges using std::ranges::sentinel_t; } // namespace STDEXEC::ranges -# include "__epilogue.hpp" - -#else - -# include - -# include "__prologue.hpp" - -namespace STDEXEC::ranges -{ - - namespace __detail - { - constexpr void begin(); - constexpr void end(); - - template - concept __has_member_begin = requires(_Ty&& __val) { static_cast<_Ty&&>(__val).begin(); }; - - template - concept __has_free_begin = __has_member_begin<_Ty> - || requires(_Ty&& __val) { begin((static_cast<_Ty&&>(__val))); }; - - template - concept __has_member_end = requires(_Ty&& __val) { static_cast<_Ty&&>(__val).end(); }; - - template - concept __has_free_end = __has_member_end<_Ty> - || requires(_Ty&& __val) { end((static_cast<_Ty&&>(__val))); }; - - struct __begin_t - { - template - requires __has_member_begin<_Range> - auto - operator()(_Range&& __rng) const noexcept(noexcept((static_cast<_Range&&>(__rng)).begin())) - -> decltype((static_cast<_Range&&>(__rng)).begin()) - { - return static_cast<_Range&&>(__rng).begin(); - } - - template - requires __has_free_begin<_Range> - auto - operator()(_Range&& __rng) const noexcept(noexcept(begin((static_cast<_Range&&>(__rng))))) - -> decltype(begin((static_cast<_Range&&>(__rng)))) - { - return begin((static_cast<_Range&&>(__rng))); - } - }; - - struct __end_t - { - template - requires __has_member_end<_Range> - auto operator()(_Range&& __rng) const noexcept(noexcept((static_cast<_Range&&>(__rng)).end())) - -> decltype((static_cast<_Range&&>(__rng)).end()) - { - return static_cast<_Range&&>(__rng).end(); - } - - template - requires __has_free_end<_Range> - auto operator()(_Range&& __rng) const noexcept(noexcept(end((static_cast<_Range&&>(__rng))))) - -> decltype(end((static_cast<_Range&&>(__rng)))) - { - return end((static_cast<_Range&&>(__rng))); - } - }; - } // namespace __detail - - inline constexpr __detail::__begin_t begin{}; - inline constexpr __detail::__end_t end{}; - - template - using iterator_t = decltype(begin((__declval<_Range>()))); - - template - using sentinel_t = decltype(end((__declval<_Range>()))); - - template - using range_reference_t = decltype(*begin((__declval<_Range>()))); - - template - using range_value_t = std::iterator_traits>::value_type; - -} // namespace STDEXEC::ranges - -# include "__epilogue.hpp" - -#endif +#include "__epilogue.hpp" From 84149a9d1853cf1e92284f1dceebc3305dfc3255 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Mon, 11 May 2026 11:16:12 -0700 Subject: [PATCH 2/3] remove support for building without c++20 ranges --- .../static_thread_pool_bulk_enqueue.cpp | 20 ++++------ ...static_thread_pool_bulk_enqueue_nested.cpp | 20 ++++------ examples/sudoku.cpp | 1 + include/exec/sequence/iterate.hpp | 40 +++++++++---------- include/exec/static_thread_pool.hpp | 12 ------ include/stdexec/__detail/__config.hpp | 9 ----- test/exec/sequence/test_iterate.cpp | 10 ++--- test/exec/sequence/test_merge.cpp | 5 --- test/exec/sequence/test_merge_each.cpp | 8 +--- .../sequence/test_merge_each_threaded.cpp | 4 -- test/exec/sequence/test_transform_each.cpp | 2 - 11 files changed, 40 insertions(+), 91 deletions(-) diff --git a/examples/benchmark/static_thread_pool_bulk_enqueue.cpp b/examples/benchmark/static_thread_pool_bulk_enqueue.cpp index 022df1103..61fd9530f 100644 --- a/examples/benchmark/static_thread_pool_bulk_enqueue.cpp +++ b/examples/benchmark/static_thread_pool_bulk_enqueue.cpp @@ -17,9 +17,8 @@ #include "./common.hpp" #include -#if !STDEXEC_NO_STDCPP_RANGES() -# include -# include +#include +#include struct RunThread { @@ -27,14 +26,14 @@ struct RunThread std::size_t total_scheds, std::size_t tid, std::barrier<>& barrier, -# ifndef STDEXEC_NO_MONOTONIC_BUFFER_RESOURCE +#ifndef STDEXEC_NO_MONOTONIC_BUFFER_RESOURCE std::span buffer, -# endif +#endif std::atomic& stop, exec::numa_policy numa) { int numa_node = numa.thread_index_to_node(tid); - numa.bind_to_node(numa_node); + void(numa.bind_to_node(numa_node)); while (true) { barrier.arrive_and_wait(); @@ -42,18 +41,18 @@ struct RunThread { break; } -# ifndef STDEXEC_NO_MONOTONIC_BUFFER_RESOURCE +#ifndef STDEXEC_NO_MONOTONIC_BUFFER_RESOURCE pmr::monotonic_buffer_resource rsrc{buffer.data(), buffer.size()}; pmr::polymorphic_allocator alloc{&rsrc}; auto env = stdexec::prop{stdexec::get_allocator, alloc}; auto [start, end] = exec::_pool_::even_share(total_scheds, tid, pool.available_parallelism()); auto iterate = exec::schedule_all(pool, std::views::iota(start, end)) | exec::ignore_all_values() | stdexec::write_env(env); -# else +#else auto [start, end] = exec::_pool_::even_share(total_scheds, tid, pool.available_parallelism()); auto iterate = exec::schedule_all(pool, std::views::iota(start, end)) | exec::ignore_all_values(); -# endif +#endif stdexec::sync_wait(iterate); barrier.arrive_and_wait(); } @@ -74,6 +73,3 @@ auto main(int argc, char** argv) -> int exec::numa_policy numa{my_numa_distribution{}}; my_main(argc, argv, std::move(numa)); } -#else -int main() {} -#endif \ No newline at end of file diff --git a/examples/benchmark/static_thread_pool_bulk_enqueue_nested.cpp b/examples/benchmark/static_thread_pool_bulk_enqueue_nested.cpp index 1ad5be451..f6fdd13ba 100644 --- a/examples/benchmark/static_thread_pool_bulk_enqueue_nested.cpp +++ b/examples/benchmark/static_thread_pool_bulk_enqueue_nested.cpp @@ -17,9 +17,8 @@ #include "./common.hpp" #include -#if !STDEXEC_NO_STDCPP_RANGES() -# include -# include +#include +#include struct RunThread { @@ -27,14 +26,14 @@ struct RunThread std::size_t total_scheds, std::size_t tid, std::barrier<>& barrier, -# ifndef STDEXEC_NO_MONOTONIC_BUFFER_RESOURCE +#ifndef STDEXEC_NO_MONOTONIC_BUFFER_RESOURCE std::span buffer, -# endif +#endif std::atomic& stop, exec::numa_policy numa) { int numa_node = numa.thread_index_to_node(tid); - numa.bind_to_node(numa_node); + void(numa.bind_to_node(numa_node)); auto scheduler = pool.get_scheduler_on_thread(tid); while (true) { @@ -43,17 +42,17 @@ struct RunThread { break; } -# ifndef STDEXEC_NO_MONOTONIC_BUFFER_RESOURCE +#ifndef STDEXEC_NO_MONOTONIC_BUFFER_RESOURCE pmr::monotonic_buffer_resource rsrc{buffer.data(), buffer.size()}; pmr::polymorphic_allocator alloc{&rsrc}; auto env = stdexec::prop{stdexec::get_allocator, alloc}; auto [start, end] = exec::_pool_::even_share(total_scheds, tid, pool.available_parallelism()); auto iterate = exec::iterate(std::views::iota(start, end)) | exec::ignore_all_values() | stdexec::write_env(env); -# else +#else auto [start, end] = exec::_pool_::even_share(total_scheds, tid, pool.available_parallelism()); auto iterate = exec::iterate(std::views::iota(start, end)) | exec::ignore_all_values(); -# endif +#endif stdexec::sync_wait(stdexec::starts_on(scheduler, iterate)); barrier.arrive_and_wait(); } @@ -64,6 +63,3 @@ auto main(int argc, char** argv) -> int { my_main(argc, argv); } -#else -int main() {} -#endif \ No newline at end of file diff --git a/examples/sudoku.cpp b/examples/sudoku.cpp index 325bccfcc..36f028f54 100644 --- a/examples/sudoku.cpp +++ b/examples/sudoku.cpp @@ -336,6 +336,7 @@ void partial_solve(stdexec::parallel_scheduler sch, { if (1 << (potential - 1) & board.get()[first_potential_set].potential_set) { + // NOLINTNEXTLINE(bugprone-unhandled-exception-at-new) std::unique_ptr new_board(new board_element[BOARD_SIZE]); copy_board(board.get(), new_board.get()); new_board.get()[first_potential_set].solved_element = potential; diff --git a/include/exec/sequence/iterate.hpp b/include/exec/sequence/iterate.hpp index f026d562a..30e5ce1db 100644 --- a/include/exec/sequence/iterate.hpp +++ b/include/exec/sequence/iterate.hpp @@ -18,26 +18,24 @@ #include "../../stdexec/__detail/__config.hpp" -#if !STDEXEC_NO_STDCPP_RANGES() - -# include "../../stdexec/__detail/__concepts.hpp" -# include "../../stdexec/__detail/__connect.hpp" -# include "../../stdexec/__detail/__env.hpp" -# include "../../stdexec/__detail/__execution_fwd.hpp" -# include "../../stdexec/__detail/__operation_states.hpp" -# include "../../stdexec/__detail/__optional.hpp" -# include "../../stdexec/__detail/__receivers.hpp" -# include "../../stdexec/__detail/__schedulers.hpp" -# include "../../stdexec/__detail/__sender_concepts.hpp" - -# include "../detail/basic_sequence.hpp" -# include "../sender_for.hpp" -# include "../sequence.hpp" -# include "../sequence_senders.hpp" -# include "../trampoline_scheduler.hpp" - -# include -# include +#include "../../stdexec/__detail/__concepts.hpp" +#include "../../stdexec/__detail/__connect.hpp" +#include "../../stdexec/__detail/__env.hpp" +#include "../../stdexec/__detail/__execution_fwd.hpp" +#include "../../stdexec/__detail/__operation_states.hpp" +#include "../../stdexec/__detail/__optional.hpp" +#include "../../stdexec/__detail/__receivers.hpp" +#include "../../stdexec/__detail/__schedulers.hpp" +#include "../../stdexec/__detail/__sender_concepts.hpp" + +#include "../detail/basic_sequence.hpp" +#include "../sender_for.hpp" +#include "../sequence.hpp" +#include "../sequence_senders.hpp" +#include "../trampoline_scheduler.hpp" + +#include +#include namespace experimental::execution { @@ -250,5 +248,3 @@ namespace experimental::execution } // namespace experimental::execution namespace exec = experimental::execution; - -#endif // !STDEXEC_NO_STDCPP_RANGES() diff --git a/include/exec/static_thread_pool.hpp b/include/exec/static_thread_pool.hpp index 6673cc619..f4e39ae5b 100644 --- a/include/exec/static_thread_pool.hpp +++ b/include/exec/static_thread_pool.hpp @@ -104,13 +104,11 @@ namespace experimental::execution return std::make_pair(static_cast(begin), static_cast(end)); } -#if !STDEXEC_NO_STDCPP_RANGES() namespace schedule_all_ { template class sequence; } // namespace schedule_all_ -#endif struct task_base { @@ -267,7 +265,6 @@ namespace experimental::execution _static_thread_pool& pool_; }; -#if !STDEXEC_NO_STDCPP_RANGES() struct _transform_iterate { template @@ -278,7 +275,6 @@ namespace experimental::execution _static_thread_pool& pool_; }; -#endif static auto _hardware_concurrency() noexcept -> unsigned int { @@ -316,7 +312,6 @@ namespace experimental::execution } } -#if !STDEXEC_NO_STDCPP_RANGES() template Sender, class Env> constexpr auto transform_sender(STDEXEC::set_value_t, Sender&& sndr, Env const & env) const noexcept @@ -340,7 +335,6 @@ namespace experimental::execution STDEXEC::_WITH_ENVIRONMENT_(Env)>(); } } -#endif }; public: @@ -1578,7 +1572,6 @@ namespace experimental::execution {} }; -#if !STDEXEC_NO_STDCPP_RANGES() namespace schedule_all_ { template @@ -1832,14 +1825,11 @@ namespace experimental::execution } // namespace schedule_all_ struct schedule_all_t; -#endif } // namespace _pool_ struct static_thread_pool : private _pool_::_static_thread_pool { -#if !STDEXEC_NO_STDCPP_RANGES() friend struct _pool_::schedule_all_t; -#endif using task_base = _pool_::task_base; static_thread_pool() = default; @@ -1872,7 +1862,6 @@ namespace experimental::execution using _pool_::_static_thread_pool::params; }; -#if !STDEXEC_NO_STDCPP_RANGES() namespace _pool_ { struct schedule_all_t @@ -1887,7 +1876,6 @@ namespace experimental::execution } // namespace _pool_ inline constexpr _pool_::schedule_all_t schedule_all{}; -#endif } // namespace experimental::execution diff --git a/include/stdexec/__detail/__config.hpp b/include/stdexec/__detail/__config.hpp index 04aa95dcd..ed768a5f9 100644 --- a/include/stdexec/__detail/__config.hpp +++ b/include/stdexec/__detail/__config.hpp @@ -592,15 +592,6 @@ namespace STDEXEC # define STDEXEC_TSAN() 0 #endif -// Before clang-16, clang did not like libstdc++'s ranges implementation -#if __has_include() && \ - (defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L) && \ - (!STDEXEC_CLANG() || STDEXEC_CLANG_VERSION >= 1600 || defined(_LIBCPP_VERSION)) -# define STDEXEC_NO_STDCPP_RANGES() 0 -#else -# define STDEXEC_NO_STDCPP_RANGES() 1 -#endif - #if __has_include() && \ (defined(__cpp_lib_memory_resource) && __cpp_lib_memory_resource >= 201603L) # define STDEXEC_NO_STDCPP_MEMORY_RESOURCE() 0 diff --git a/test/exec/sequence/test_iterate.cpp b/test/exec/sequence/test_iterate.cpp index 4dfb3e03e..fa5a06d25 100644 --- a/test/exec/sequence/test_iterate.cpp +++ b/test/exec/sequence/test_iterate.cpp @@ -18,11 +18,9 @@ #include "exec/sequence/iterate.hpp" #include "stdexec/execution.hpp" -#if !STDEXEC_NO_STDCPP_RANGES() - -# include -# include -# include +#include +#include +#include namespace { @@ -143,5 +141,3 @@ namespace } } // namespace - -#endif // !STDEXEC_NO_STDCPP_RANGES() diff --git a/test/exec/sequence/test_merge.cpp b/test/exec/sequence/test_merge.cpp index 767418d68..cc93c384f 100644 --- a/test/exec/sequence/test_merge.cpp +++ b/test/exec/sequence/test_merge.cpp @@ -28,12 +28,9 @@ #include "exec/trampoline_scheduler.hpp" #include "stdexec/__detail/__continues_on.hpp" #include "stdexec/__detail/__just.hpp" -#include "stdexec/__detail/__meta.hpp" #include "stdexec/__detail/__upon_error.hpp" -#include #include -#include #include #include #include @@ -141,7 +138,6 @@ namespace CHECK(count == 2); } -#if !STDEXEC_NO_STDCPP_RANGES() TEST_CASE("merge - merge sender merges all items", "[sequence_senders][merge][iterate]") { auto range = [](auto from, auto to) @@ -231,7 +227,6 @@ namespace CHECK(total == 12570); CHECK(count == 60); } -#endif struct my_domain { diff --git a/test/exec/sequence/test_merge_each.cpp b/test/exec/sequence/test_merge_each.cpp index 1caec69ef..8497f238a 100644 --- a/test/exec/sequence/test_merge_each.cpp +++ b/test/exec/sequence/test_merge_each.cpp @@ -194,8 +194,6 @@ namespace }); }; -#if !STDEXEC_NO_STDCPP_RANGES() - // a sequence of numbers from itoa() [[maybe_unused]] static constexpr auto range = [](auto from, auto to) @@ -356,7 +354,7 @@ namespace } // TODO - fix problem with stopping -# if 0 +#if 0 TEST_CASE( "merge_each - merge_each sender stops when a nested sequence fails", "[sequence_senders][static_thread_pool][merge_each][merge][iterate]") { @@ -380,8 +378,6 @@ namespace CHECK(count == 20); CHECK(v.has_value() == false); } -# endif // 0 - -#endif // !STDEXEC_NO_STDCPP_RANGES() +#endif // 0 } // namespace diff --git a/test/exec/sequence/test_merge_each_threaded.cpp b/test/exec/sequence/test_merge_each_threaded.cpp index 2ac0bb4e1..1da6a4903 100644 --- a/test/exec/sequence/test_merge_each_threaded.cpp +++ b/test/exec/sequence/test_merge_each_threaded.cpp @@ -198,8 +198,6 @@ namespace }); }; -#if !STDEXEC_NO_STDCPP_RANGES() - // a sequence of numbers from itoa() [[maybe_unused]] static constexpr auto range = [](auto from, auto to) @@ -406,6 +404,4 @@ namespace CHECK(v.has_value() == false); } -#endif // !STDEXEC_NO_STDCPP_RANGES() - } // namespace diff --git a/test/exec/sequence/test_transform_each.cpp b/test/exec/sequence/test_transform_each.cpp index f5fd43eee..6a80b2671 100644 --- a/test/exec/sequence/test_transform_each.cpp +++ b/test/exec/sequence/test_transform_each.cpp @@ -75,7 +75,6 @@ namespace CHECK(value == 42); } -#if !STDEXEC_NO_STDCPP_RANGES() TEST_CASE("transform_each - transform sender applies adaptor to each item", "[sequence_senders][transform_each][iterate]") { @@ -92,7 +91,6 @@ namespace ex::sync_wait(exec::ignore_all_values(sum)); CHECK(total == 45); } -#endif struct my_domain { From b2a4131f5509a7cf2c60e64214ce4866c84c649e Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Mon, 11 May 2026 11:43:12 -0700 Subject: [PATCH 3/3] use range algorithms where appropriate --- include/exec/static_thread_pool.hpp | 15 ++++----------- include/nvexec/stream/algorithm_base.cuh | 5 +++-- include/stdexec/__detail/__deprecations.hpp | 8 ++++++-- include/stdexec/__detail/__ranges.hpp | 19 +++++++++++-------- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/include/exec/static_thread_pool.hpp b/include/exec/static_thread_pool.hpp index f4e39ae5b..1c2ec1b9c 100644 --- a/include/exec/static_thread_pool.hpp +++ b/include/exec/static_thread_pool.hpp @@ -759,8 +759,7 @@ namespace experimental::execution .thread_index = index}); } - // NOLINTNEXTLINE(modernize-use-ranges) we still support platforms without the std::ranges algorithms - std::sort(thread_index_by_numa_node_.begin(), thread_index_by_numa_node_.end()); + std::ranges::sort(thread_index_by_numa_node_); std::vector victims{}; for (auto& state: thread_states_) { @@ -836,15 +835,12 @@ namespace experimental::execution inline auto _static_thread_pool::num_threads(int numa) const noexcept -> std::size_t { thread_index_by_numa_node key{.numa_node = numa, .thread_index = 0}; - // NOLINTNEXTLINE(modernize-use-ranges) we still support platforms without the std::ranges algorithms - auto it = std::lower_bound(thread_index_by_numa_node_.begin(), - thread_index_by_numa_node_.end(), - key); + auto it = std::ranges::lower_bound(thread_index_by_numa_node_, key); if (it == thread_index_by_numa_node_.end()) { return 0; } - auto it_end = std::upper_bound(it, thread_index_by_numa_node_.end(), key); + auto it_end = std::ranges::upper_bound(it, thread_index_by_numa_node_.end(), key); return static_cast(std::distance(it, it_end)); } @@ -870,10 +866,7 @@ namespace experimental::execution -> std::size_t { thread_index_by_numa_node key{.numa_node = node_index, .thread_index = 0}; - // NOLINTNEXTLINE(modernize-use-ranges) we still support platforms without the std::ranges algorithms - auto it = std::lower_bound(thread_index_by_numa_node_.begin(), - thread_index_by_numa_node_.end(), - key); + auto it = std::ranges::lower_bound(thread_index_by_numa_node_, key); STDEXEC_ASSERT(it != thread_index_by_numa_node_.end()); std::advance(it, thread_index); return it->thread_index; diff --git a/include/nvexec/stream/algorithm_base.cuh b/include/nvexec/stream/algorithm_base.cuh index 8832c1a84..b49b5d568 100644 --- a/include/nvexec/stream/algorithm_base.cuh +++ b/include/nvexec/stream/algorithm_base.cuh @@ -18,9 +18,10 @@ #pragma once -#include "../../stdexec/__detail/__ranges.hpp" #include "../../stdexec/execution.hpp" + #include +#include #include @@ -32,7 +33,7 @@ namespace nv::execution::_strm::__algo_range_init_fun { template using binary_invoke_result_t = ::cuda::std::decay_t< - ::cuda::std::invoke_result_t, InitT>>; + ::cuda::std::invoke_result_t, InitT>>; template struct receiver : public stream_receiver_base diff --git a/include/stdexec/__detail/__deprecations.hpp b/include/stdexec/__detail/__deprecations.hpp index 83d1c27c4..9bd9a9ea0 100644 --- a/include/stdexec/__detail/__deprecations.hpp +++ b/include/stdexec/__detail/__deprecations.hpp @@ -16,6 +16,7 @@ #pragma once #include "__execution_fwd.hpp" +#include "__ranges.hpp" // IWYU pragma: keep #include "__prologue.hpp" @@ -27,8 +28,8 @@ namespace STDEXEC inline constexpr __execute_may_block_caller_t const & execute_may_block_caller = __execute_may_block_caller; - using empty_env [[deprecated( - "STDEXEC::empty_env is now spelled " STDEXEC_PP_STRINGIZE(STDEXEC) "::env<>")]] = env<>; + using empty_env + [[deprecated("empty_env is now spelled " STDEXEC_PP_STRINGIZE(STDEXEC) "::env<>")]] = env<>; using dependent_completions [[deprecated("use dependent_sender_error instead of " "dependent_completions")]] = dependent_sender_error; @@ -86,6 +87,9 @@ namespace STDEXEC [[deprecated]] inline constexpr exec::__execute_t const & execute = exec::__execute; + + namespace [[deprecated("Use std::ranges directly")]] ranges + {} } // namespace STDEXEC #include "__epilogue.hpp" diff --git a/include/stdexec/__detail/__ranges.hpp b/include/stdexec/__detail/__ranges.hpp index fdbe5a2ec..fc85e66a1 100644 --- a/include/stdexec/__detail/__ranges.hpp +++ b/include/stdexec/__detail/__ranges.hpp @@ -21,15 +21,18 @@ #include "__prologue.hpp" -namespace STDEXEC::ranges +namespace STDEXEC { - using std::ranges::begin; - using std::ranges::end; + namespace [[deprecated("use std::ranges directly")]] ranges + { + using std::ranges::begin; + using std::ranges::end; - using std::ranges::range_value_t; - using std::ranges::range_reference_t; - using std::ranges::iterator_t; - using std::ranges::sentinel_t; -} // namespace STDEXEC::ranges + using std::ranges::range_value_t; + using std::ranges::range_reference_t; + using std::ranges::iterator_t; + using std::ranges::sentinel_t; + } // namespace ranges +} // namespace STDEXEC #include "__epilogue.hpp"