From bf50d16439078684635677471e96a04a06d9f402 Mon Sep 17 00:00:00 2001 From: "Ryan M. Richard" Date: Wed, 23 Apr 2025 15:57:10 -0500 Subject: [PATCH 01/10] backup --- include/parallelzone/hardware/cpu/cpu.hpp | 11 + include/parallelzone/task/argument_traits.hpp | 53 +++++ .../parallelzone/task/argument_wrapper.hpp | 111 ++++++++++ .../task/detail_/task_wrapper_.hpp | 83 ++++++++ include/parallelzone/task/task.hpp | 3 + include/parallelzone/task/task_wrapper.hpp | 84 ++++++++ .../parallelzone/hardware/cpu/cpu.cpp | 0 .../parallelzone/task/argument_traits.cpp | 108 ++++++++++ .../parallelzone/task/argument_wrapper.cpp | 106 ++++++++++ .../task/detail_/task_wrapper_.cpp | 200 ++++++++++++++++++ .../parallelzone/task/task_wrapper.cpp | 68 ++++++ 11 files changed, 827 insertions(+) create mode 100644 include/parallelzone/hardware/cpu/cpu.hpp create mode 100644 include/parallelzone/task/argument_traits.hpp create mode 100644 include/parallelzone/task/argument_wrapper.hpp create mode 100644 include/parallelzone/task/detail_/task_wrapper_.hpp create mode 100644 include/parallelzone/task/task.hpp create mode 100644 include/parallelzone/task/task_wrapper.hpp create mode 100644 tests/cxx/unit_tests/parallelzone/hardware/cpu/cpu.cpp create mode 100644 tests/cxx/unit_tests/parallelzone/task/argument_traits.cpp create mode 100644 tests/cxx/unit_tests/parallelzone/task/argument_wrapper.cpp create mode 100644 tests/cxx/unit_tests/parallelzone/task/detail_/task_wrapper_.cpp create mode 100644 tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp diff --git a/include/parallelzone/hardware/cpu/cpu.hpp b/include/parallelzone/hardware/cpu/cpu.hpp new file mode 100644 index 00000000..d5c5675b --- /dev/null +++ b/include/parallelzone/hardware/cpu/cpu.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace parallelzone::hardware { + +struct ProfileInformation {}; + +class CPU {}; + +} // namespace parallelzone::hardware \ No newline at end of file diff --git a/include/parallelzone/task/argument_traits.hpp b/include/parallelzone/task/argument_traits.hpp new file mode 100644 index 00000000..48a5c1b9 --- /dev/null +++ b/include/parallelzone/task/argument_traits.hpp @@ -0,0 +1,53 @@ +#pragma once +#include + +namespace parallelzone::task { + +/** @brief Structure that works out cv-qualifiers and reference-ness of @p T. + * + * @tparam The type being inspected. + * + * This structure is predominantly intended for use in template contexts where + * the properties of a type are not known. In those contexts, it can be + * useful to know if a type @p T is a reference, a const reference etc. + */ +template +struct ArgumentTraits { + /// Type of @p T w/o reference and cv-qualifiers + using value_type = std::decay_t; + + /// Type of a mutable reference to an object of type @p value_type + using reference = value_type&; + + /// Type of a read-only reference to an object of type @p value_type + using const_reference = const value_type&; + + /// Type of an rvalue reference to an object of type @p value_type + using rvalue_reference = value_type&&; + + /// Type of a pointer to an object of type @p value_type + using pointer = value_type*; + + /// Type of a pointer to a read-only object of type @p value_type + using const_pointer = const value_type*; + + /// Is @p T consistent with being passed by value? + static constexpr bool is_value_v = std::is_same_v; + + /// Is @p T consistent with being passed as a const value? + static constexpr bool is_const_value_v = + std::is_same_v; + + /// Is @p T consistent with being passed by reference? + static constexpr bool is_reference_v = std::is_same_v; + + /// Is @p T consistent with being passed by const reference? + static constexpr bool is_const_reference_v = + std::is_same_v; + + /// Is @p T consistent with being passed by rvalue reference? + static constexpr bool is_rvalue_reference_v = + std::is_same_v; +}; + +} // namespace parallelzone::task \ No newline at end of file diff --git a/include/parallelzone/task/argument_wrapper.hpp b/include/parallelzone/task/argument_wrapper.hpp new file mode 100644 index 00000000..ff849d5f --- /dev/null +++ b/include/parallelzone/task/argument_wrapper.hpp @@ -0,0 +1,111 @@ +#pragma once +#include + +namespace parallelzone::task { + +/** @brief Class to help forward arguments for generic functions. + * + * @tparam The type of the object *this will wrap. + * + * Depending on how the user passes an argument to a function, the function + * will own the value or alias it. ParallelZone is designed to generically + * interact with functions and in turn will need to capture arguments. When + * PZ captures an argument it will need to prolong the lifetime if the user + * passed the argument by value or rvalue reference, and maintain the aliasing + * if the user passed the argument by reference. + * + * The ArgumentWrapper class handles the logic of potentially prolonging the + * lifetime of the argument, while providing a consistent API regardless of + * whether or not it is owning or aliasing the argument. + */ +template +class ArgumentWrapper { +private: + /// Is @p U the same type as *this? + template + static constexpr bool is_arg_wrapper_v = + std::is_same_v, U>; + + /// Disables a template function if @p U is the same type as *this + template + using disable_if_arg_wrapper = std::enable_if_t>; + +public: + /// Type traits of @p T needed for TMP + using traits_t = ArgumentTraits; + + /// Unqualified type of @p T + using value_type = typename traits_t::value_type; + + /// Type of a mutable reference to an object of type @p value_type + using reference = typename traits_t::reference; + + /// Type of a read-only reference to an object of type @p value_type + using const_reference = typename traits_t::const_reference; + + /** @brief Creates a new object which wraps @p object. + * + * @tparam U The type of object must be implicitly convertible to @p T. + * @tparam Used to disable this ctor via SFINAE if @p U is + * an object of type ArgumentWrapper. + * + * The value ctor wraps the provided argument, automatically prolonging + * its lifetime if needed (needed when @p T is a value or rvalue + * reference). If an extended lifetime is not needed, @p object will be + * aliased and the user is responsible for ensuring @p object is alive + * for the duration of *this. + * + * @param object The value *this is wrapping. + * + * @throw None No throw guarantee. + */ + template>> + explicit ArgumentWrapper(U&& object) : m_value_(std::forward(object)) {} + + /** @brief Provides potentially mutable access to the value. + * + * This method is used to access the value. The result will be mutable if + * @p T is not const-qualified and read-only if @p T is const-qualified. + * + * @return A reference to the wrapped value. + * + * @throw None No throw guarantee. + */ + auto& value() { return m_value_; } + + /** @brief Provides read-only access to the value. + * + * This method is identical to the non-const version except that the result + * is guarantee to be read-only. + * + * @return A read-only reference to the wrapped value. + * + * @throw None No throw guarantee. + */ + const auto& value() const { return m_value_; } + +private: + /// Hold by value? + static constexpr bool hold_by_value = + traits_t::is_value_v || traits_t::is_rvalue_reference_v; + + /// Hold by reference? + static constexpr bool hold_by_reference = traits_t::is_reference_v; + + /// Hold by const reference? + static constexpr bool hold_by_const_reference = + traits_t::is_const_reference_v; + + /// Type, if being held by reference + using reference_holder = + std::conditional_t; + + /// Works out the type of the holder + using holder_type = + std::conditional_t; + + /// The actual value being held. + holder_type m_value_; +}; + +} // namespace parallelzone::task \ No newline at end of file diff --git a/include/parallelzone/task/detail_/task_wrapper_.hpp b/include/parallelzone/task/detail_/task_wrapper_.hpp new file mode 100644 index 00000000..e620742a --- /dev/null +++ b/include/parallelzone/task/detail_/task_wrapper_.hpp @@ -0,0 +1,83 @@ +#pragma once +#include +#include +#include +namespace parallelzone::task::detail_ { + +/** @brief Creates a tuple that will forward arguments. + * + * @tparams Args The types of @p args + * + * In order to store arguments we need to put them in ArgumentWrapper objects + * (which will manage the lifetime). This function forwards a parameter pack + * into a tuple of ArgumentWrapper objects. + * + * @param[in] args The arguments to wrap into a tuple. + * + * @throw None No throw guarantee. + */ +template +static auto wrap_args_(Args&&... args) { + return std::tuple{ArgumentWrapper(std::forward(args))...}; +} + +/// Implements apply_ by expanding tuple. See other overload +template +static auto apply_(FxnType&& fxn, std::tuple...> args, + std::index_sequence) { + return fxn(std::forward(std::get(args).value())...); +} + +/** @brief Calls a callable with the provided arguments. + * + * @tparam FxnType The type of the callable. + * @tparam Args The types of the arguments as the user wants them to be passed. + * + * Most functions expect positional arguments and not a tuple of arguments, + * but tuples are more convenient for meta-programming. This function will + * take a tuple and unpack it into the positional arguments of the provided + * function, and then call the function. + * + * @note This function reimplements std::apply because we need to additionally + * call `.value()` on each element of the tuple. + * + * @param[in] fxn The callable to call. + * @param[in] args A tuple containing the arguments to forward to @p fxn. + * + * @return Returns if @p fxn returns. + * + * @throws Throws if @p fxn throws. Same throw guarantee. + */ +template +static auto apply_(FxnType&& fxn, std::tuple...> args) { + return apply_(std::forward(fxn), std::move(args), + std::make_index_sequence()); +} + +template +static auto make_inner_lambda_(FxnType&& fxn, Args&&... args) { + return [fxn = std::forward(fxn), + args = wrap_args_(std::forward(args)...)]() { + apply_(fxn, std::move(args)); + }; +} + +template +static auto make_outer_lambda_(FxnType&& fxn, Args&&... args) { + auto inner_lambda = make_inner_lambda_(std::forward(fxn), + std::forward(args)...); + + using result_type = decltype(inner_lambda()); + if constexpr(std::is_same_v) { + return [inner_lambda = std::move(inner_lambda)]() { + inner_lambda(); + return std::any{}; + }; + } else { + return [inner_lambda = std::move(inner_lambda)]() { + return std::make_any(inner_lambda()); + }; + } +} + +} // namespace parallelzone::task::detail_ \ No newline at end of file diff --git a/include/parallelzone/task/task.hpp b/include/parallelzone/task/task.hpp new file mode 100644 index 00000000..a1708231 --- /dev/null +++ b/include/parallelzone/task/task.hpp @@ -0,0 +1,3 @@ +#pragma once +#include +#include \ No newline at end of file diff --git a/include/parallelzone/task/task_wrapper.hpp b/include/parallelzone/task/task_wrapper.hpp new file mode 100644 index 00000000..7d029683 --- /dev/null +++ b/include/parallelzone/task/task_wrapper.hpp @@ -0,0 +1,84 @@ +#pragma once +#include +#include + +namespace parallelzone::task { + +/** @brief Infrastructure for type-erasing a task. + * + * A lot of ParallelZone revolves around scheduling, running, and measuring + * "tasks". To PZ, a task is nothing more than a call to a callable. The + * callable may or may not return something and it may have side-effects (e.g., + * logging). The TaskWrapper class provides a generic type-erased interface for + * interacting with the task. + * + * @code + * // Say you want to call function foo with arguments arg0, arg1. Then the + * // usage is: + * auto [task, unwrapper] = make_task(foo, arg0, arg1); + * + * // Run the task + * auto result = task(); + * + * // Get the result + * auto unwrapped_result = unwrapper(result); + * @endcode + * + * It should be noted that wrapping functions with zero arguments is also + * possible, just pass the name of the function to make_task. You can + * + */ +class TaskWrapper { +private: +public: + /// TMP for working out the result of the task + template + using return_type = + decltype(std::declval()(std::declval()...)); + + /// Type used to return the results of the task + using type_erased_return_type = std::any; + + template + TaskWrapper(FxnType&& fxn, Args&&... args) : + m_erased_function_(detail_::make_outer_lambda_( + std::forward(fxn), std::forward(args)...)) {} + + type_erased_return_type operator()() { return m_erased_function_(); } + + /** @brief Creates a function which can unwrap a type-erased result. + * + * The resulting function takes an rvalue reference to the type-erased + * result (i.e., you'll probably need to move the result into the function) + * and will return the object that was inside the type-erased, not a copy + * or reference. In turn you should NOT use the type-erased result again + * after calling the function. + */ + template + static auto unwrapper() { + return [](type_erased_return_type&& returned_object) { + if constexpr(std::is_same_v) { + return; + } else { + return std::any_cast(std::move(returned_object)); + } + }; + } + +private: + /// Type *this will use to hold the task + using erased_function_type = std::function; + + /// Type type-erased task + erased_function_type m_erased_function_; +}; + +template +auto make_task(FxnType&& fxn, Args&&... args) { + using return_type = TaskWrapper::return_type; + auto unwrapper = TaskWrapper::unwrapper(); + TaskWrapper t(std::forward(fxn), std::forward(args)...); + return std::make_pair(std::move(t), std::move(unwrapper)); +} + +} // namespace parallelzone::task \ No newline at end of file diff --git a/tests/cxx/unit_tests/parallelzone/hardware/cpu/cpu.cpp b/tests/cxx/unit_tests/parallelzone/hardware/cpu/cpu.cpp new file mode 100644 index 00000000..e69de29b diff --git a/tests/cxx/unit_tests/parallelzone/task/argument_traits.cpp b/tests/cxx/unit_tests/parallelzone/task/argument_traits.cpp new file mode 100644 index 00000000..3e9008d7 --- /dev/null +++ b/tests/cxx/unit_tests/parallelzone/task/argument_traits.cpp @@ -0,0 +1,108 @@ +#include "../catch.hpp" +#include +#include +#include + +using namespace parallelzone::task; + +TEST_CASE("ArgumentTraits") { + SECTION("value") { + using type = int; + using traits_t = ArgumentTraits; + + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE( + std::is_same_v); + STATIC_REQUIRE( + std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE( + std::is_same_v); + STATIC_REQUIRE(traits_t::is_value_v); + STATIC_REQUIRE_FALSE(traits_t::is_const_value_v); + STATIC_REQUIRE_FALSE(traits_t::is_reference_v); + STATIC_REQUIRE_FALSE(traits_t::is_const_reference_v); + STATIC_REQUIRE_FALSE(traits_t::is_rvalue_reference_v); + } + + SECTION("const value") { + using type = int; + using traits_t = ArgumentTraits; + + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE( + std::is_same_v); + STATIC_REQUIRE( + std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE( + std::is_same_v); + STATIC_REQUIRE_FALSE(traits_t::is_value_v); + STATIC_REQUIRE(traits_t::is_const_value_v); + STATIC_REQUIRE_FALSE(traits_t::is_reference_v); + STATIC_REQUIRE_FALSE(traits_t::is_const_reference_v); + STATIC_REQUIRE_FALSE(traits_t::is_rvalue_reference_v); + } + + SECTION("reference") { + using type = int; + using traits_t = ArgumentTraits; + + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE( + std::is_same_v); + STATIC_REQUIRE( + std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE( + std::is_same_v); + STATIC_REQUIRE_FALSE(traits_t::is_value_v); + STATIC_REQUIRE_FALSE(traits_t::is_const_value_v); + STATIC_REQUIRE(traits_t::is_reference_v); + STATIC_REQUIRE_FALSE(traits_t::is_const_reference_v); + STATIC_REQUIRE_FALSE(traits_t::is_rvalue_reference_v); + } + + SECTION("const reference") { + using type = int; + using traits_t = ArgumentTraits; + + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE( + std::is_same_v); + STATIC_REQUIRE( + std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE( + std::is_same_v); + STATIC_REQUIRE_FALSE(traits_t::is_value_v); + STATIC_REQUIRE_FALSE(traits_t::is_const_value_v); + STATIC_REQUIRE_FALSE(traits_t::is_reference_v); + STATIC_REQUIRE(traits_t::is_const_reference_v); + STATIC_REQUIRE_FALSE(traits_t::is_rvalue_reference_v); + } + + SECTION("rvalue reference") { + using type = int; + using traits_t = ArgumentTraits; + + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE( + std::is_same_v); + STATIC_REQUIRE( + std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE( + std::is_same_v); + STATIC_REQUIRE_FALSE(traits_t::is_value_v); + STATIC_REQUIRE_FALSE(traits_t::is_const_value_v); + STATIC_REQUIRE_FALSE(traits_t::is_reference_v); + STATIC_REQUIRE_FALSE(traits_t::is_const_reference_v); + STATIC_REQUIRE(traits_t::is_rvalue_reference_v); + } +} \ No newline at end of file diff --git a/tests/cxx/unit_tests/parallelzone/task/argument_wrapper.cpp b/tests/cxx/unit_tests/parallelzone/task/argument_wrapper.cpp new file mode 100644 index 00000000..63d1c52a --- /dev/null +++ b/tests/cxx/unit_tests/parallelzone/task/argument_wrapper.cpp @@ -0,0 +1,106 @@ +#include "../catch.hpp" +#include +#include +#include + +using namespace parallelzone::task; + +TEST_CASE("ArgumentWrapper") { + SECTION("Ctors") { + using T = std::vector; + T value{1, 2, 3}; + + ArgumentWrapper arg_wrapper(value); + auto pdata = arg_wrapper.value().data(); + REQUIRE(arg_wrapper.value() == value); + REQUIRE(pdata != value.data()); + + SECTION("Copy ctor") { + ArgumentWrapper copies(arg_wrapper); + REQUIRE(copies.value() == value); + REQUIRE(copies.value().data() != pdata); + } + + SECTION("Move ctor") { + ArgumentWrapper moves(std::move(arg_wrapper)); + REQUIRE(moves.value() == value); + REQUIRE(moves.value().data() == pdata); + } + + SECTION("Copy assignment") { + ArgumentWrapper copies(T{7, 8, 9}); + auto pcopies = &(copies = arg_wrapper); + REQUIRE(copies.value() == value); + REQUIRE(copies.value().data() != pdata); + REQUIRE(pcopies == &copies); + } + + SECTION("Move assignment") { + ArgumentWrapper moves(T{7, 8, 9}); + auto pmoves = &(moves = std::move(arg_wrapper)); + REQUIRE(moves.value() == value); + REQUIRE(moves.value().data() == pdata); + REQUIRE(pmoves == &moves); + } + } + + SECTION("T == int") { + using T = int; + SECTION("By value") { + ArgumentWrapper by_value(1); + REQUIRE(by_value.value() == 1); + REQUIRE(std::as_const(by_value).value() == 1); + } + + SECTION("By reference") { + int value = 2; + ArgumentWrapper by_ref(value); + REQUIRE(&by_ref.value() == &value); + REQUIRE(&std::as_const(by_ref).value() == &value); + } + + SECTION("By const reference") { + int value = 2; + ArgumentWrapper by_cref(value); + REQUIRE(&by_cref.value() == &value); + REQUIRE(&std::as_const(by_cref).value() == &value); + } + + SECTION("By rvalue reference") { + int value = 2; + ArgumentWrapper by_rref(std::move(value)); + REQUIRE(by_rref.value() == value); + REQUIRE(std::as_const(by_rref).value() == value); + } + } + + SECTION("T == std::vector") { + using T = std::vector; + T value{1, 2, 3}; + + SECTION("By value") { + ArgumentWrapper by_value(T{1, 2, 3}); + REQUIRE(by_value.value() == value); + REQUIRE(std::as_const(by_value).value() == value); + } + + SECTION("By reference") { + ArgumentWrapper by_ref(value); + REQUIRE(&by_ref.value() == &value); + REQUIRE(&std::as_const(by_ref).value() == &value); + } + + SECTION("By const reference") { + ArgumentWrapper by_cref(value); + REQUIRE(&by_cref.value() == &value); + REQUIRE(&std::as_const(by_cref).value() == &value); + } + + SECTION("By rvalue reference") { + auto pvalue = value.data(); + ArgumentWrapper by_rref(std::move(value)); + REQUIRE(by_rref.value().data() == pvalue); + REQUIRE(std::as_const(by_rref).value().data() == pvalue); + } + } +} \ No newline at end of file diff --git a/tests/cxx/unit_tests/parallelzone/task/detail_/task_wrapper_.cpp b/tests/cxx/unit_tests/parallelzone/task/detail_/task_wrapper_.cpp new file mode 100644 index 00000000..88426b6a --- /dev/null +++ b/tests/cxx/unit_tests/parallelzone/task/detail_/task_wrapper_.cpp @@ -0,0 +1,200 @@ +#include "../../catch.hpp" +#include +#include +#include + +using namespace parallelzone::task; + +namespace { + +using vector_type = std::vector; + +} // namespace + +TEST_CASE("wrap_args_") { + int an_int = 1; + vector_type a_vector{1, 2, 3}; + auto pa_vector = a_vector.data(); + + SECTION("No args") { + auto rv = detail_::wrap_args_(); + REQUIRE(rv == std::make_tuple()); + } + + SECTION("by value") { + auto int_rv = detail_::wrap_args_(1); + REQUIRE(std::get<0>(int_rv).value() == 1); + + auto vector_rv = detail_::wrap_args_(vector_type{1, 2, 3}); + REQUIRE(std::get<0>(vector_rv).value() == a_vector); + REQUIRE(std::get<0>(vector_rv).value().data() != pa_vector); + } + + SECTION("by reference") { + auto int_rv = detail_::wrap_args_(an_int); + REQUIRE(std::get<0>(int_rv).value() == 1); + REQUIRE(&std::get<0>(int_rv).value() == &an_int); + + auto vector_rv = detail_::wrap_args_(a_vector); + REQUIRE(std::get<0>(vector_rv).value() == a_vector); + REQUIRE(&std::get<0>(vector_rv).value() == &a_vector); + } + + SECTION("by const reference") { + auto int_rv = detail_::wrap_args_(std::as_const(an_int)); + REQUIRE(std::get<0>(int_rv).value() == 1); + REQUIRE(&std::get<0>(int_rv).value() == &an_int); + + auto vector_rv = detail_::wrap_args_(std::as_const(a_vector)); + REQUIRE(std::get<0>(vector_rv).value() == a_vector); + REQUIRE(&std::get<0>(vector_rv).value() == &a_vector); + } + + SECTION("by rvalue reference") { + auto vector_rv = detail_::wrap_args_(std::move(a_vector)); + REQUIRE(std::get<0>(vector_rv).value().data() == pa_vector); + } +} + +TEST_CASE("apply_") { + vector_type a_vector{1, 2, 3}; + auto pa_vector = a_vector.data(); + + SECTION("Function takes no arguments") { + auto inputs = detail_::wrap_args_(); + auto lambda = []() {}; + REQUIRE_NOTHROW(detail_::apply_(lambda, std::move(inputs))); + } + + SECTION("Function takes by value and passed by value") { + auto inputs = detail_::wrap_args_(vector_type{1, 2, 3}); + auto lambda = [&a_vector](vector_type x) { REQUIRE(x == a_vector); }; + REQUIRE_NOTHROW(detail_::apply_(lambda, std::move(inputs))); + } + + SECTION("Function takes by value and passed by reference") { + auto inputs = detail_::wrap_args_(a_vector); + auto lambda = [&a_vector](vector_type x) { + REQUIRE(x == a_vector); + REQUIRE(x.data() != a_vector.data()); + }; + REQUIRE_NOTHROW(detail_::apply_(lambda, std::move(inputs))); + } + + SECTION("Function takes by value and passed by const reference") { + auto inputs = detail_::wrap_args_(std::as_const(a_vector)); + auto lambda = [&a_vector](vector_type x) { + REQUIRE(x == a_vector); + REQUIRE(x.data() != a_vector.data()); + }; + REQUIRE_NOTHROW(detail_::apply_(lambda, std::move(inputs))); + } + + SECTION("Function takes by value and passed by rvalue") { + auto inputs = detail_::wrap_args_(std::move(a_vector)); + auto lambda = [pa_vector](vector_type x) { + REQUIRE(x.data() == pa_vector); + }; + REQUIRE_NOTHROW(detail_::apply_(lambda, std::move(inputs))); + } + + // Should raise a compiler error + // SECTION("Function takes by reference and passed by value") { + // auto inputs = detail_::wrap_args_(vector_type{1, 2, 3}); + // auto lambda = [&a_vector](vector_type& x) { }; + // REQUIRE_NOTHROW(detail_::apply_(lambda, std::move(inputs))); + //} + + SECTION("Function takes by reference and passed by reference") { + auto inputs = detail_::wrap_args_(a_vector); + auto lambda = [&a_vector](vector_type& x) { REQUIRE(&x == &a_vector); }; + REQUIRE_NOTHROW(detail_::apply_(lambda, std::move(inputs))); + } + + // Should raise a compiler error + // SECTION("Function takes by reference and passed by const reference") { + // auto inputs = detail_::wrap_args_(std::as_const(a_vector)); + // auto lambda = [&a_vector](vector_type& x) { }; + // REQUIRE_NOTHROW(detail_::apply_(lambda, std::move(inputs))); + // } + + // Should raise a compiler error + // SECTION("Function takes by reference and passed by rvalue reference") { + // auto inputs = detail_::wrap_args_(std::move(a_vector)); + // auto lambda = [pa_vector](vector_type& x) { }; + // REQUIRE_NOTHROW(detail_::apply_(lambda, std::move(inputs))); + // } + + SECTION("Function takes by const reference and passed by value") { + auto inputs = detail_::wrap_args_(vector_type{1, 2, 3}); + auto lambda = [&a_vector](const vector_type& x) { + REQUIRE(x == a_vector); + }; + REQUIRE_NOTHROW(detail_::apply_(lambda, std::move(inputs))); + } + + SECTION("Function takes by const reference and passed by reference") { + auto inputs = detail_::wrap_args_(a_vector); + auto lambda = [&a_vector](const vector_type& x) { + REQUIRE(&x == &a_vector); + }; + REQUIRE_NOTHROW(detail_::apply_(lambda, std::move(inputs))); + } + + SECTION("Function takes by const reference and passed by const reference") { + auto inputs = detail_::wrap_args_(std::as_const(a_vector)); + auto lambda = [&a_vector](const vector_type& x) { + REQUIRE(&x == &a_vector); + }; + REQUIRE_NOTHROW(detail_::apply_(lambda, std::move(inputs))); + } + + SECTION( + "Function takes by const reference and passed by rvalue reference") { + auto inputs = detail_::wrap_args_(std::move(a_vector)); + auto lambda = [pa_vector](const vector_type& x) { + REQUIRE(x.data() == pa_vector); + }; + REQUIRE_NOTHROW(detail_::apply_(lambda, std::move(inputs))); + } + + SECTION("Function takes by rvalue reference and passed by value") { + auto inputs = detail_::wrap_args_(vector_type{1, 2, 3}); + auto lambda = [&a_vector](vector_type&& x) { REQUIRE(x == a_vector); }; + REQUIRE_NOTHROW(detail_::apply_(lambda, std::move(inputs))); + } + + // Should raise a compiler error + // SECTION("Function takes by rvalue reference and passed by reference") { + // auto inputs = detail_::wrap_args_(a_vector); + // auto lambda = [&a_vector](vector_type&& x) {}; + // REQUIRE_NOTHROW(detail_::apply_(lambda, std::move(inputs))); + // } + + // Should raise a compiler error + // SECTION( + // "Function takes by rvalue reference and passed by const reference") { + // auto inputs = detail_::wrap_args_(std::as_const(a_vector)); + // auto lambda = [&a_vector](vector_type&& x) {}; + // REQUIRE_NOTHROW(detail_::apply_(lambda, std::move(inputs))); + // } + + SECTION( + "Function takes by rvalue reference and passed by rvalue reference") { + auto inputs = detail_::wrap_args_(std::move(a_vector)); + auto lambda = [pa_vector](vector_type&& x) { + REQUIRE(x.data() == pa_vector); + }; + REQUIRE_NOTHROW(detail_::apply_(lambda, std::move(inputs))); + } + + SECTION("Function returns by value") { + auto inputs = detail_::wrap_args_(std::move(a_vector)); + auto lambda = [pa_vector](vector_type x) { + REQUIRE(x.data() == pa_vector); + return std::move(x); + }; + auto rv = detail_::apply_(lambda, std::move(inputs)); + REQUIRE(rv.data() == pa_vector); + } +} \ No newline at end of file diff --git a/tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp b/tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp new file mode 100644 index 00000000..d784e07e --- /dev/null +++ b/tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp @@ -0,0 +1,68 @@ +#include "../catch.hpp" +#include +#include +#include + +using namespace parallelzone::task; + +namespace { + +using vector_type = std::vector; + +// Free functions with no returns and various inputs +void no_return_free0() {} +void no_return_free1(int) {} +void no_return_free2(int* pv, vector_type v) { REQUIRE(v.data() == pv); } + +} // namespace + +using type_erased_return_type = TaskWrapper::type_erased_return_type; + +TEST_CASE("TaskWrapper") { + vector_type a_vector{1, 2, 3}; + auto pa_vector = a_vector.data(); + + // SECTION("Constructor") { + // TaskWrapper t(no_return_free2, pa_vector, std::move(a_vector)); + // // t(); + // } + + SECTION("unwrapper") { + SECTION("no result, i.e., void") { + auto u = TaskWrapper::unwrapper(); + std::any empty; + REQUIRE_NOTHROW(u(std::move(empty))); + } + + SECTION("int") { + auto u = TaskWrapper::unwrapper(); + auto da_any = std::make_any(1); + REQUIRE(u(std::move(da_any)) == 1); + } + + SECTION("vector") { + auto u = TaskWrapper::unwrapper(); + auto da_any = std::make_any(std::move(a_vector)); + REQUIRE(std::any_cast(da_any).data() == pa_vector); + REQUIRE(u(std::move(da_any)).data() == pa_vector); + } + } + + SECTION("No return") { + auto tester = [](auto&& task, auto&& unwrapper) { + type_erased_return_type result; + REQUIRE_NOTHROW(result = task()); + REQUIRE_FALSE(result.has_value()); + REQUIRE_NOTHROW(unwrapper(std::move(result))); + }; + + std::apply(tester, make_task(no_return_free0)); + std::apply(tester, make_task(no_return_free1, 1)); + + // This one makes sure that a moved object is forwarded all the way + // into the task. + auto task = make_task(no_return_free2, pa_vector, std::move(a_vector)); + // std::get<0>(task)(); + // std::apply(tester, std::move(task)); + } +} \ No newline at end of file From aceb413a12197dd1e99b6a9e837afd9d784b3401 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 23 Apr 2025 21:01:21 +0000 Subject: [PATCH 02/10] Committing clang-format changes --- include/parallelzone/hardware/cpu/cpu.hpp | 16 ++++++++++++++++ include/parallelzone/task/argument_traits.hpp | 16 ++++++++++++++++ include/parallelzone/task/argument_wrapper.hpp | 16 ++++++++++++++++ .../parallelzone/task/detail_/task_wrapper_.hpp | 16 ++++++++++++++++ include/parallelzone/task/task.hpp | 16 ++++++++++++++++ include/parallelzone/task/task_wrapper.hpp | 16 ++++++++++++++++ .../unit_tests/parallelzone/hardware/cpu/cpu.cpp | 15 +++++++++++++++ .../parallelzone/task/argument_traits.cpp | 16 ++++++++++++++++ .../parallelzone/task/argument_wrapper.cpp | 16 ++++++++++++++++ .../parallelzone/task/detail_/task_wrapper_.cpp | 16 ++++++++++++++++ .../parallelzone/task/task_wrapper.cpp | 16 ++++++++++++++++ 11 files changed, 175 insertions(+) diff --git a/include/parallelzone/hardware/cpu/cpu.hpp b/include/parallelzone/hardware/cpu/cpu.hpp index d5c5675b..34b73b82 100644 --- a/include/parallelzone/hardware/cpu/cpu.hpp +++ b/include/parallelzone/hardware/cpu/cpu.hpp @@ -1,3 +1,19 @@ +/* + * Copyright 2025 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include diff --git a/include/parallelzone/task/argument_traits.hpp b/include/parallelzone/task/argument_traits.hpp index 48a5c1b9..616849e6 100644 --- a/include/parallelzone/task/argument_traits.hpp +++ b/include/parallelzone/task/argument_traits.hpp @@ -1,3 +1,19 @@ +/* + * Copyright 2025 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include diff --git a/include/parallelzone/task/argument_wrapper.hpp b/include/parallelzone/task/argument_wrapper.hpp index ff849d5f..9f932d31 100644 --- a/include/parallelzone/task/argument_wrapper.hpp +++ b/include/parallelzone/task/argument_wrapper.hpp @@ -1,3 +1,19 @@ +/* + * Copyright 2025 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include diff --git a/include/parallelzone/task/detail_/task_wrapper_.hpp b/include/parallelzone/task/detail_/task_wrapper_.hpp index e620742a..615e3e86 100644 --- a/include/parallelzone/task/detail_/task_wrapper_.hpp +++ b/include/parallelzone/task/detail_/task_wrapper_.hpp @@ -1,3 +1,19 @@ +/* + * Copyright 2025 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include #include diff --git a/include/parallelzone/task/task.hpp b/include/parallelzone/task/task.hpp index a1708231..3ae5d8a6 100644 --- a/include/parallelzone/task/task.hpp +++ b/include/parallelzone/task/task.hpp @@ -1,3 +1,19 @@ +/* + * Copyright 2025 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include #include \ No newline at end of file diff --git a/include/parallelzone/task/task_wrapper.hpp b/include/parallelzone/task/task_wrapper.hpp index 7d029683..6b4a46ec 100644 --- a/include/parallelzone/task/task_wrapper.hpp +++ b/include/parallelzone/task/task_wrapper.hpp @@ -1,3 +1,19 @@ +/* + * Copyright 2025 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include #include diff --git a/tests/cxx/unit_tests/parallelzone/hardware/cpu/cpu.cpp b/tests/cxx/unit_tests/parallelzone/hardware/cpu/cpu.cpp index e69de29b..049b76d2 100644 --- a/tests/cxx/unit_tests/parallelzone/hardware/cpu/cpu.cpp +++ b/tests/cxx/unit_tests/parallelzone/hardware/cpu/cpu.cpp @@ -0,0 +1,15 @@ +/* + * Copyright 2025 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ diff --git a/tests/cxx/unit_tests/parallelzone/task/argument_traits.cpp b/tests/cxx/unit_tests/parallelzone/task/argument_traits.cpp index 3e9008d7..3ca2c391 100644 --- a/tests/cxx/unit_tests/parallelzone/task/argument_traits.cpp +++ b/tests/cxx/unit_tests/parallelzone/task/argument_traits.cpp @@ -1,3 +1,19 @@ +/* + * Copyright 2025 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "../catch.hpp" #include #include diff --git a/tests/cxx/unit_tests/parallelzone/task/argument_wrapper.cpp b/tests/cxx/unit_tests/parallelzone/task/argument_wrapper.cpp index 63d1c52a..98838b0b 100644 --- a/tests/cxx/unit_tests/parallelzone/task/argument_wrapper.cpp +++ b/tests/cxx/unit_tests/parallelzone/task/argument_wrapper.cpp @@ -1,3 +1,19 @@ +/* + * Copyright 2025 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "../catch.hpp" #include #include diff --git a/tests/cxx/unit_tests/parallelzone/task/detail_/task_wrapper_.cpp b/tests/cxx/unit_tests/parallelzone/task/detail_/task_wrapper_.cpp index 88426b6a..84040ce6 100644 --- a/tests/cxx/unit_tests/parallelzone/task/detail_/task_wrapper_.cpp +++ b/tests/cxx/unit_tests/parallelzone/task/detail_/task_wrapper_.cpp @@ -1,3 +1,19 @@ +/* + * Copyright 2025 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "../../catch.hpp" #include #include diff --git a/tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp b/tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp index d784e07e..b84370c0 100644 --- a/tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp +++ b/tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp @@ -1,3 +1,19 @@ +/* + * Copyright 2025 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "../catch.hpp" #include #include From 1e7a50b3ca50ac1df941f6b8e36fb8908a118818 Mon Sep 17 00:00:00 2001 From: "Ryan M. Richard" Date: Wed, 23 Apr 2025 22:36:41 -0500 Subject: [PATCH 03/10] make_inner_lambda_ works --- .../task/detail_/task_wrapper_.hpp | 39 ++++++++++++++----- .../task/detail_/task_wrapper_.cpp | 20 +++++++++- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/include/parallelzone/task/detail_/task_wrapper_.hpp b/include/parallelzone/task/detail_/task_wrapper_.hpp index e620742a..f3847c39 100644 --- a/include/parallelzone/task/detail_/task_wrapper_.hpp +++ b/include/parallelzone/task/detail_/task_wrapper_.hpp @@ -17,14 +17,14 @@ namespace parallelzone::task::detail_ { * @throw None No throw guarantee. */ template -static auto wrap_args_(Args&&... args) { +auto wrap_args_(Args&&... args) { return std::tuple{ArgumentWrapper(std::forward(args))...}; } /// Implements apply_ by expanding tuple. See other overload template -static auto apply_(FxnType&& fxn, std::tuple...> args, - std::index_sequence) { +auto apply_(FxnType&& fxn, std::tuple...> args, + std::index_sequence) { return fxn(std::forward(std::get(args).value())...); } @@ -49,17 +49,36 @@ static auto apply_(FxnType&& fxn, std::tuple...> args, * @throws Throws if @p fxn throws. Same throw guarantee. */ template -static auto apply_(FxnType&& fxn, std::tuple...> args) { +auto apply_(FxnType&& fxn, std::tuple...> args) { return apply_(std::forward(fxn), std::move(args), std::make_index_sequence()); } +/** @brief Creates a lambda that wil forward @p args to @p fxn. + * + * @tparam FxnType A callable type. + * @tparam Args The types of args + * + * This function creates a lambda that can be called with no arguments. When + * the lambda is called, @p args will be forwarded to @p fxn and the result + * returned from the lambda, i.e., this function creates a lambda that can be + * used to run @p fxn lazily. + * + * @param[in] fxn The callable to run. + * @param[in] args The arguments to forward to @p fxn. + * + * @return A lambda that wraps the process of forwarding @p args to @p fxn. + * + * @throw None No throw guarantee. + */ template -static auto make_inner_lambda_(FxnType&& fxn, Args&&... args) { - return [fxn = std::forward(fxn), - args = wrap_args_(std::forward(args)...)]() { - apply_(fxn, std::move(args)); +auto make_inner_lambda_(FxnType&& fxn, Args&&... args) { + auto wrapped_args = wrap_args_(std::forward(args)...); + auto l = [fxn = std::forward(fxn), + args = std::move(wrapped_args)]() mutable { + return apply_(fxn, std::move(args)); }; + return l; } template @@ -69,12 +88,12 @@ static auto make_outer_lambda_(FxnType&& fxn, Args&&... args) { using result_type = decltype(inner_lambda()); if constexpr(std::is_same_v) { - return [inner_lambda = std::move(inner_lambda)]() { + return [inner_lambda = std::move(inner_lambda)]() mutable { inner_lambda(); return std::any{}; }; } else { - return [inner_lambda = std::move(inner_lambda)]() { + return [inner_lambda = std::move(inner_lambda)]() mutable { return std::make_any(inner_lambda()); }; } diff --git a/tests/cxx/unit_tests/parallelzone/task/detail_/task_wrapper_.cpp b/tests/cxx/unit_tests/parallelzone/task/detail_/task_wrapper_.cpp index 88426b6a..f04b3e95 100644 --- a/tests/cxx/unit_tests/parallelzone/task/detail_/task_wrapper_.cpp +++ b/tests/cxx/unit_tests/parallelzone/task/detail_/task_wrapper_.cpp @@ -197,4 +197,22 @@ TEST_CASE("apply_") { auto rv = detail_::apply_(lambda, std::move(inputs)); REQUIRE(rv.data() == pa_vector); } -} \ No newline at end of file +} + +TEST_CASE("make_inner_lambda_") { + // This function is largely implemented by wrap_args_ and apply_. As long + // as those two functions work this function should work. Point being we + // just spot check a round-trip of the input + + vector_type a_vector{1, 2, 3}; + auto pa_vector = a_vector.data(); + + auto lambda = [pa_vector](vector_type x) { + REQUIRE(x.data() == pa_vector); + return std::move(x); + }; + auto lambda2 = detail_::make_inner_lambda_(lambda, std::move(a_vector)); + REQUIRE(lambda2().data() == pa_vector); +} + +TEST_CASE("make_outer_lambda_") {} \ No newline at end of file From 6d49b27593026775e23851e7384d968ceb2dcd09 Mon Sep 17 00:00:00 2001 From: "Ryan M. Richard" Date: Thu, 24 Apr 2025 11:39:54 -0500 Subject: [PATCH 04/10] backup --- .../task/detail_/task_wrapper_.hpp | 19 +++ include/parallelzone/task/task_wrapper.hpp | 141 ++++++++++++++++-- .../task/detail_/task_wrapper_.cpp | 25 +++- .../parallelzone/task/task_wrapper.cpp | 64 +++++++- 4 files changed, 229 insertions(+), 20 deletions(-) diff --git a/include/parallelzone/task/detail_/task_wrapper_.hpp b/include/parallelzone/task/detail_/task_wrapper_.hpp index 4dacb9dc..7f5c6953 100644 --- a/include/parallelzone/task/detail_/task_wrapper_.hpp +++ b/include/parallelzone/task/detail_/task_wrapper_.hpp @@ -97,6 +97,25 @@ auto make_inner_lambda_(FxnType&& fxn, Args&&... args) { return l; } +/** @brief Wraps the process of type-erasing a lazy function call. + * + * @tparam FxnType The type of the callable. + * @tparam Args The types of the arguments to forward to the function. + * + * This is the top-level function implementing the type-erasure of lazily + * executing a call. It relies on `make_inner_lambda_` to create the lambda + * capable of lazily running the call. The lambda from make_inner_lambda_ still + * retains the return type, which is what the lambda from this function + * erases. The lambda resulting from this function has the signature + * `std::any lambda();`. + * + * @param[in] fxn The callable to wrap. + * @param[in] args The arguments to pass to the callable when it is executed. + * + * @return A type-erased callable, that when run will execute @p fxn. + * + * @throw None No throw guarantee. + */ template static auto make_outer_lambda_(FxnType&& fxn, Args&&... args) { auto inner_lambda = make_inner_lambda_(std::forward(fxn), diff --git a/include/parallelzone/task/task_wrapper.hpp b/include/parallelzone/task/task_wrapper.hpp index 6b4a46ec..1e8ce12a 100644 --- a/include/parallelzone/task/task_wrapper.hpp +++ b/include/parallelzone/task/task_wrapper.hpp @@ -23,7 +23,7 @@ namespace parallelzone::task { /** @brief Infrastructure for type-erasing a task. * * A lot of ParallelZone revolves around scheduling, running, and measuring - * "tasks". To PZ, a task is nothing more than a call to a callable. The + * "tasks". To PZ, a task is nothing more than executing a callable. The * callable may or may not return something and it may have side-effects (e.g., * logging). The TaskWrapper class provides a generic type-erased interface for * interacting with the task. @@ -46,29 +46,110 @@ namespace parallelzone::task { */ class TaskWrapper { private: -public: - /// TMP for working out the result of the task - template - using return_type = - decltype(std::declval()(std::declval()...)); + /// Is type @p U the same as TaskWrapper? + template + static constexpr bool is_task_wrapper_v = std::is_same_v; + /// Disables a method via SFINAE if @p U is TaskWrapper + template + using disable_if_task_wrapper_t = std::enable_if_t>; + +public: /// Type used to return the results of the task using type_erased_return_type = std::any; - template - TaskWrapper(FxnType&& fxn, Args&&... args) : + /** @brief Value ctor. Creates a TaskWrapper given a callable and arguments. + * + * @tparam FxnType The type of the callable *this will wrap. + * @tparam Args The types of the arguments to forward to the wrapped + * callable. + * @tparam Used to disable this method via SFINAE if + * @p FxnType is TaskWrapper. + * + * Users are encouraged to rely on the `make_task` free function for + * creating TaskWrapper objects. That freed function relies on this ctor. + * This ctor will create a TensorWrapper object which wraps the provided + * callable, @p fxn, and the arguments to pass to @p fxn, @p args. When + * *this is executed via `operator()()` @p args will be forwarded to + * @p fxn and the result returned as an object of type + * @p type_erased_return_type. + * + * @param[in] fxn The callable object this will wrap. + * @param[in] args The arguments to forward to @p fxn when *this is + * executed. + * + * @throw ??? Function may throw if capturing @p fxn or @p args throws. + * This should be very unlikely though as captures are typically + * by reference. Strong throw guarantee. + */ + template>> + explicit TaskWrapper(FxnType&& fxn, Args&&... args) : m_erased_function_(detail_::make_outer_lambda_( std::forward(fxn), std::forward(args)...)) {} + /** @brief Takes ownership of another task. + * + * This method will transfer the task wrapped by a different TaskWrapper + * object into *this. The TaskWrapper that *this takes the object from + * will no longer own the task and is in a valid, but otherwise unspecified + * state. + * + * @param[in,out] other The object to take the task from. After this + * operation @p other is in a valid, but otherwise + * unspecified state. + * + * @throw None No throw guarantee. + */ + TaskWrapper(TaskWrapper&& other) noexcept = default; + + /** @brief Overrides the task in *this with that in @p rhs. + * + * This method will release the task currently wrapped by *this and + * replace it with the task wrapped by @p rhs. The task is taken from + * @p rhs so that *this owns it and @p rhs no longer does. After this + * operation @p rhs is in a valid, but otherwise unspecified state. + * + * @param[in,out] rhs The object to take the task from. After this + * operation @p rhs is in a valid, but otherwise + * unspecified state. + * + * @return *this after overriding the task. + * + * @throw None no throw guarantee. + */ + TaskWrapper& operator=(TaskWrapper&& rhs) noexcept = default; + + /** @brief Runs the task held by *this. + * + * TaskWrapper objects allow lazy execution the wrapped task. Calling this + * method will execute the task. Generally speaking, tasks are single use, + * so this function should only be called once. The TaskWrapper class does + * NOT enforce this because without knowledge of the wrapped function's + * implementation it can not know for sure whether this is the case or + * not. + * + * @return A type erased return. If the wrapped function does not return, + * the resulting object should NOT be unwrapped. + * + * @throw ??? Throws if the wrapped function throws. Same throw guarantee. + */ type_erased_return_type operator()() { return m_erased_function_(); } /** @brief Creates a function which can unwrap a type-erased result. * - * The resulting function takes an rvalue reference to the type-erased - * result (i.e., you'll probably need to move the result into the function) - * and will return the object that was inside the type-erased, not a copy - * or reference. In turn you should NOT use the type-erased result again - * after calling the function. + * @tparam ReturnType The type to unwrap the object as. + * + * This function creates a callable that takes an rvalue reference to a + * @p type_erased_return_type object and returns the object that was + * inside the object. Note that the return from the callable is the object, + * not a copy or reference. In turn the object passed to the callable is + * consumed and should NOT be used again. + * + * @return A callable capable of unwrapping a @p type_erased_return_type + * object into a @p ReturnType object. + * + * @throw None No throw guarantee. */ template static auto unwrapper() { @@ -82,6 +163,10 @@ class TaskWrapper { } private: + /// Deleted because there can only be one instance of a task. + TaskWrapper(const TaskWrapper&) = delete; + TaskWrapper& operator=(const TaskWrapper&) = delete; + /// Type *this will use to hold the task using erased_function_type = std::function; @@ -89,10 +174,36 @@ class TaskWrapper { erased_function_type m_erased_function_; }; +/** @brief Wraps a task in a TaskWrapper object. + * + * @related TaskWrapper + * + * @tparam FxnType The type of the callable that *this will wrap. + * @tparam Args The types of the arguments that will be forwarded to the + * callable. + * + * To interact generically with a task, ParallelZone requires the task to be + * wrapped in a TaskWrapper object. When a TaskWrapper object executes its + * task the results of the task, if any, are type-erased too. To restore + * their types TaskWrapper objects can generate unwrapping callables. While + * TaskWrapper objects and unwrapping callables can be made directly through + * the TaskWrapper API, the resulting calls are template-heavy. This free + * function relies on a little bit of under-the-hood template meta-programming + * to take care of the templating for the caller, thus providing a nicer API. + * + * @param[in] fxn The callable to wrap. + * @param[in] args The arguments to forward to @p fxn. + * + * @return The task and a callable for unwrapping the result of the task. + * + * @throw ??? This function may throw if capturing the arguments throws. This + * should be a very unlikely occurrence. Strong throw guarantee. + */ template auto make_task(FxnType&& fxn, Args&&... args) { - using return_type = TaskWrapper::return_type; - auto unwrapper = TaskWrapper::unwrapper(); + using return_type = + decltype(std::declval()(std::declval()...)); + auto unwrapper = TaskWrapper::unwrapper(); TaskWrapper t(std::forward(fxn), std::forward(args)...); return std::make_pair(std::move(t), std::move(unwrapper)); } diff --git a/tests/cxx/unit_tests/parallelzone/task/detail_/task_wrapper_.cpp b/tests/cxx/unit_tests/parallelzone/task/detail_/task_wrapper_.cpp index 10151ec6..883c9067 100644 --- a/tests/cxx/unit_tests/parallelzone/task/detail_/task_wrapper_.cpp +++ b/tests/cxx/unit_tests/parallelzone/task/detail_/task_wrapper_.cpp @@ -231,4 +231,27 @@ TEST_CASE("make_inner_lambda_") { REQUIRE(lambda2().data() == pa_vector); } -TEST_CASE("make_outer_lambda_") {} \ No newline at end of file +TEST_CASE("make_outer_lambda_") { + // This function is largely implemented by make_inner_lambda_. Here we just + // check it wraps the return correctly + + vector_type a_vector{1, 2, 3}; + auto pa_vector = a_vector.data(); + + SECTION("Doesn't return") { + auto lambda = [pa_vector](vector_type x) { + REQUIRE(x.data() == pa_vector); + }; + auto lambda2 = detail_::make_outer_lambda_(lambda, std::move(a_vector)); + REQUIRE_FALSE(lambda2().has_value()); + } + + SECTION("Returns") { + auto lambda = [pa_vector](vector_type x) { + REQUIRE(x.data() == pa_vector); + return std::move(x); + }; + auto lambda2 = detail_::make_outer_lambda_(lambda, std::move(a_vector)); + REQUIRE(std::any_cast(lambda2()).data() == pa_vector); + } +} \ No newline at end of file diff --git a/tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp b/tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp index b84370c0..bb68e096 100644 --- a/tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp +++ b/tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp @@ -30,6 +30,19 @@ void no_return_free0() {} void no_return_free1(int) {} void no_return_free2(int* pv, vector_type v) { REQUIRE(v.data() == pv); } +vector_type return_free2(int* pv, vector_type v) { + REQUIRE(v.data() == pv); + return std::move(v); +} + +struct TestFunctor { + TestFunctor(vector_type v) : m_v(std::move(v)) {} + + vector_type operator()() { return std::move(m_v); } + + vector_type m_v; +}; + } // namespace using type_erased_return_type = TaskWrapper::type_erased_return_type; @@ -38,10 +51,53 @@ TEST_CASE("TaskWrapper") { vector_type a_vector{1, 2, 3}; auto pa_vector = a_vector.data(); - // SECTION("Constructor") { - // TaskWrapper t(no_return_free2, pa_vector, std::move(a_vector)); - // // t(); - // } + SECTION("Ctor") { + SECTION("value") { + // This is heavily tested throughout the rest of the test case. + // So just spot check we can pass different function types + + SECTION("lambda") { + auto l = [pa_vector](vector_type v) { + REQUIRE(v.data() == pa_vector); + }; + TaskWrapper t(l, std::move(a_vector)); + t(); + } + SECTION("free function") { + TaskWrapper t(no_return_free2, pa_vector, std::move(a_vector)); + t(); + } + SECTION("Functor") { + TestFunctor fxn(std::move(a_vector)); + TaskWrapper t(std::move(fxn)); + t(); + } + } + + SECTION("move") { + TaskWrapper t(no_return_free2, pa_vector, std::move(a_vector)); + TaskWrapper t_moved(std::move(t)); + t_moved(); + } + + SECTION("move assignment") { + TaskWrapper t(return_free2, pa_vector, std::move(a_vector)); + TaskWrapper t_moved(no_return_free0); + auto pt_moved = &(t_moved = std::move(t)); + auto rv = t_moved(); + REQUIRE(std::any_cast(rv).data() == pa_vector); + REQUIRE(pt_moved == &t_moved); + } + } + + SECTION("operator()") { + // This method just runs the wrapped function and returns the + // result. As long as make_outer_lambda_ works, this method should + // work and we just spot check + TaskWrapper t(return_free2, pa_vector, std::move(a_vector)); + auto rv = t(); + REQUIRE(std::any_cast(rv).data() == pa_vector); + } SECTION("unwrapper") { SECTION("no result, i.e., void") { From 7fdc16a06ce0944ceffffdef4179590420acb56a Mon Sep 17 00:00:00 2001 From: "Ryan M. Richard" Date: Thu, 24 Apr 2025 22:21:31 -0500 Subject: [PATCH 05/10] r2g --- include/parallelzone/hardware/cpu/cpu.hpp | 76 ++++++++++++++++++- include/parallelzone/hardware/hardware.hpp | 3 + include/parallelzone/logging/logging.hpp | 3 + include/parallelzone/parallelzone.hpp | 5 +- src/parallelzone/hardware/cpu/cpu.cpp | 14 ++++ .../parallelzone/hardware/cpu/cpu.cpp | 35 +++++++++ .../parallelzone/task/task_wrapper.cpp | 18 ++++- 7 files changed, 145 insertions(+), 9 deletions(-) create mode 100644 include/parallelzone/hardware/hardware.hpp create mode 100644 include/parallelzone/logging/logging.hpp create mode 100644 src/parallelzone/hardware/cpu/cpu.cpp diff --git a/include/parallelzone/hardware/cpu/cpu.hpp b/include/parallelzone/hardware/cpu/cpu.hpp index 34b73b82..0adb2382 100644 --- a/include/parallelzone/hardware/cpu/cpu.hpp +++ b/include/parallelzone/hardware/cpu/cpu.hpp @@ -15,13 +15,81 @@ */ #pragma once - -#include +#include +#include namespace parallelzone::hardware { -struct ProfileInformation {}; +/** @brief Aggregates known profiling information. + * + * The class is primarily intended to ensure that `profile_it` can maintain a + * consistent API as new profiling information is added, i.e., + * `ProfileInformation` is intended to be an opaque type whose content can + * grow over time. + */ +struct ProfileInformation { + /// Type used to measure time durations + using duration = std::chrono::high_resolution_clock::duration; + + /// Long the function ran for + duration wall_time; +}; + +/** @brief Class representing a central processing unit (CPU). + * + * This class is intended to be a runtime interface for interacting with the + * CPU. Right now it can be used to profile a task run on a CPU. + */ +class CPU { +private: + /// Type used internally for wrapping tasks + using task_type = task::TaskWrapper; + + /// Type used internally for returning type-erased results + using result_type = typename task_type::type_erased_return_type; + +public: + /// Type containing profiling information + using profile_information = ProfileInformation; + + /** @brief Profiles a function. + * + * @tparam FxnType The type of the callable to profile. + * @tparam Args The types of the arguments to forward to the callable. + * + * @param[in] fxn The callable to run. + * @param[in] args The arguments to forward to the callable. + * + * @return A pair whose first element is the return of your callable and + * the second element is the profiling information obtained from + * running the callable. If your function does not have a return + * only the profiling info is returned. + * + * @throw ??? Throws if the callable throws. Same throw guarantee. + */ + template + auto profile_it(FxnType&& fxn, Args&&... args) const { + auto&& [t, unwrapper] = task::make_task(std::forward(fxn), + std::forward(args)...); + auto result = profile_it_(std::move(t)); + + using result_type = decltype(unwrapper(std::move(result.first))); + + if constexpr(std::is_same_v) { + return std::move(result.second); + } else { + auto unwrapped_result = unwrapper(std::move(result.first)); + return std::make_pair(std::move(unwrapped_result), + std::move(result.second)); + } + } + +private: + /// How the type-erased profile_it_ method returns its results + using profile_return_type = std::pair; -class CPU {}; + /// Returns the result of the task and the profiling information obtained + profile_return_type profile_it_(task_type&& task) const; +}; } // namespace parallelzone::hardware \ No newline at end of file diff --git a/include/parallelzone/hardware/hardware.hpp b/include/parallelzone/hardware/hardware.hpp new file mode 100644 index 00000000..96d9557b --- /dev/null +++ b/include/parallelzone/hardware/hardware.hpp @@ -0,0 +1,3 @@ +#pragma once +#include +#include diff --git a/include/parallelzone/logging/logging.hpp b/include/parallelzone/logging/logging.hpp new file mode 100644 index 00000000..087b05fd --- /dev/null +++ b/include/parallelzone/logging/logging.hpp @@ -0,0 +1,3 @@ +#pragma once +#include +#include \ No newline at end of file diff --git a/include/parallelzone/parallelzone.hpp b/include/parallelzone/parallelzone.hpp index c6666633..3860a633 100644 --- a/include/parallelzone/parallelzone.hpp +++ b/include/parallelzone/parallelzone.hpp @@ -23,7 +23,10 @@ * library. */ -#include "parallelzone/serialization.hpp" +#include +#include #include +#include +#include namespace parallelzone {} // namespace parallelzone diff --git a/src/parallelzone/hardware/cpu/cpu.cpp b/src/parallelzone/hardware/cpu/cpu.cpp new file mode 100644 index 00000000..2accb562 --- /dev/null +++ b/src/parallelzone/hardware/cpu/cpu.cpp @@ -0,0 +1,14 @@ +#include + +namespace parallelzone::hardware { + +typename CPU::profile_return_type CPU::profile_it_(task_type&& task) const { + profile_information i; + const auto t1 = std::chrono::high_resolution_clock::now(); + auto result = task(); + const auto t2 = std::chrono::high_resolution_clock::now(); + i.wall_time = (t2 - t1); + return std::make_pair(std::move(result), std::move(i)); +} + +} // namespace parallelzone::hardware \ No newline at end of file diff --git a/tests/cxx/unit_tests/parallelzone/hardware/cpu/cpu.cpp b/tests/cxx/unit_tests/parallelzone/hardware/cpu/cpu.cpp index 049b76d2..c492ae50 100644 --- a/tests/cxx/unit_tests/parallelzone/hardware/cpu/cpu.cpp +++ b/tests/cxx/unit_tests/parallelzone/hardware/cpu/cpu.cpp @@ -13,3 +13,38 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +#include "../../catch.hpp" +#include + +using namespace parallelzone; + +TEST_CASE("CPU") { + hardware::CPU defaulted; + + SECTION("profile_it") { + using vector_type = std::vector; + vector_type a_vector{1, 2, 3}; + auto pa_vector = a_vector.data(); + + SECTION("No return") { + auto l = [pa_vector](vector_type v) { + REQUIRE(v.data() == pa_vector); + }; + + auto info = defaulted.profile_it(l, std::move(a_vector)); + REQUIRE(info.wall_time.count() > 0); // Should have taken time... + } + + SECTION("Has return") { + auto l = [pa_vector](vector_type v) { + REQUIRE(v.data() == pa_vector); + return std::move(v); + }; + + auto&& [rv, info] = defaulted.profile_it(l, std::move(a_vector)); + REQUIRE(rv.data() == pa_vector); // Test there's no hidden copies + REQUIRE(info.wall_time.count() > 0); // Should have taken time... + } + } +} \ No newline at end of file diff --git a/tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp b/tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp index bb68e096..d807f0f3 100644 --- a/tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp +++ b/tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp @@ -129,12 +129,22 @@ TEST_CASE("TaskWrapper") { }; std::apply(tester, make_task(no_return_free0)); + std::apply(tester, make_task(no_return_free1, 1)); - // This one makes sure that a moved object is forwarded all the way - // into the task. auto task = make_task(no_return_free2, pa_vector, std::move(a_vector)); - // std::get<0>(task)(); - // std::apply(tester, std::move(task)); + std::apply(tester, std::move(task)); + } + + SECTION("return") { + auto tester = [pa_vector](auto&& task, auto&& unwrapper) { + type_erased_return_type result; + REQUIRE_NOTHROW(result = task()); + REQUIRE(result.has_value()); + REQUIRE(unwrapper(std::move(result)).data() == pa_vector); + }; + + auto task = make_task(return_free2, pa_vector, std::move(a_vector)); + std::apply(tester, std::move(task)); } } \ No newline at end of file From c488c9a927132612004d06598ac69e69786958a0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 25 Apr 2025 03:22:19 +0000 Subject: [PATCH 06/10] Committing clang-format changes --- include/parallelzone/hardware/hardware.hpp | 16 ++++++++++++++++ include/parallelzone/logging/logging.hpp | 16 ++++++++++++++++ include/parallelzone/task/task_wrapper.hpp | 2 +- src/parallelzone/hardware/cpu/cpu.cpp | 16 ++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/include/parallelzone/hardware/hardware.hpp b/include/parallelzone/hardware/hardware.hpp index 96d9557b..c7dc0fa7 100644 --- a/include/parallelzone/hardware/hardware.hpp +++ b/include/parallelzone/hardware/hardware.hpp @@ -1,3 +1,19 @@ +/* + * Copyright 2025 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include #include diff --git a/include/parallelzone/logging/logging.hpp b/include/parallelzone/logging/logging.hpp index 087b05fd..ca0b97c8 100644 --- a/include/parallelzone/logging/logging.hpp +++ b/include/parallelzone/logging/logging.hpp @@ -1,3 +1,19 @@ +/* + * Copyright 2025 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include #include \ No newline at end of file diff --git a/include/parallelzone/task/task_wrapper.hpp b/include/parallelzone/task/task_wrapper.hpp index 1e8ce12a..1fa5f793 100644 --- a/include/parallelzone/task/task_wrapper.hpp +++ b/include/parallelzone/task/task_wrapper.hpp @@ -164,7 +164,7 @@ class TaskWrapper { private: /// Deleted because there can only be one instance of a task. - TaskWrapper(const TaskWrapper&) = delete; + TaskWrapper(const TaskWrapper&) = delete; TaskWrapper& operator=(const TaskWrapper&) = delete; /// Type *this will use to hold the task diff --git a/src/parallelzone/hardware/cpu/cpu.cpp b/src/parallelzone/hardware/cpu/cpu.cpp index 2accb562..7adb8c2c 100644 --- a/src/parallelzone/hardware/cpu/cpu.cpp +++ b/src/parallelzone/hardware/cpu/cpu.cpp @@ -1,3 +1,19 @@ +/* + * Copyright 2025 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include namespace parallelzone::hardware { From 2513410d1f233aa63e2084bc2a339174a34c1803 Mon Sep 17 00:00:00 2001 From: "Ryan M. Richard" Date: Thu, 24 Apr 2025 22:22:41 -0500 Subject: [PATCH 07/10] add missing header --- include/parallelzone/task/task_wrapper.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/parallelzone/task/task_wrapper.hpp b/include/parallelzone/task/task_wrapper.hpp index 1e8ce12a..480159aa 100644 --- a/include/parallelzone/task/task_wrapper.hpp +++ b/include/parallelzone/task/task_wrapper.hpp @@ -16,6 +16,7 @@ #pragma once #include +#include #include namespace parallelzone::task { From e7f55abdbccf5bf59c5d2c7188a71afdf4832fc4 Mon Sep 17 00:00:00 2001 From: "Ryan M. Richard" Date: Thu, 24 Apr 2025 22:32:08 -0500 Subject: [PATCH 08/10] remove redundant move --- tests/cxx/unit_tests/parallelzone/hardware/cpu/cpu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cxx/unit_tests/parallelzone/hardware/cpu/cpu.cpp b/tests/cxx/unit_tests/parallelzone/hardware/cpu/cpu.cpp index c492ae50..c5a86e23 100644 --- a/tests/cxx/unit_tests/parallelzone/hardware/cpu/cpu.cpp +++ b/tests/cxx/unit_tests/parallelzone/hardware/cpu/cpu.cpp @@ -39,7 +39,7 @@ TEST_CASE("CPU") { SECTION("Has return") { auto l = [pa_vector](vector_type v) { REQUIRE(v.data() == pa_vector); - return std::move(v); + return v; }; auto&& [rv, info] = defaulted.profile_it(l, std::move(a_vector)); From 4893aaa14096748ad6be5d1a8f70a7b2619c618f Mon Sep 17 00:00:00 2001 From: "Ryan M. Richard" Date: Thu, 24 Apr 2025 22:35:57 -0500 Subject: [PATCH 09/10] remove another move --- tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp b/tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp index d807f0f3..8fd599e8 100644 --- a/tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp +++ b/tests/cxx/unit_tests/parallelzone/task/task_wrapper.cpp @@ -32,7 +32,7 @@ void no_return_free2(int* pv, vector_type v) { REQUIRE(v.data() == pv); } vector_type return_free2(int* pv, vector_type v) { REQUIRE(v.data() == pv); - return std::move(v); + return v; } struct TestFunctor { From c1fc4f67836af89b8b44ac8cc529b34f90a8d01a Mon Sep 17 00:00:00 2001 From: "Ryan M. Richard" Date: Thu, 24 Apr 2025 22:43:14 -0500 Subject: [PATCH 10/10] remove even more moves... --- .../unit_tests/parallelzone/task/detail_/task_wrapper_.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/cxx/unit_tests/parallelzone/task/detail_/task_wrapper_.cpp b/tests/cxx/unit_tests/parallelzone/task/detail_/task_wrapper_.cpp index 883c9067..b4a99d60 100644 --- a/tests/cxx/unit_tests/parallelzone/task/detail_/task_wrapper_.cpp +++ b/tests/cxx/unit_tests/parallelzone/task/detail_/task_wrapper_.cpp @@ -208,7 +208,7 @@ TEST_CASE("apply_") { auto inputs = detail_::wrap_args_(std::move(a_vector)); auto lambda = [pa_vector](vector_type x) { REQUIRE(x.data() == pa_vector); - return std::move(x); + return x; }; auto rv = detail_::apply_(lambda, std::move(inputs)); REQUIRE(rv.data() == pa_vector); @@ -225,7 +225,7 @@ TEST_CASE("make_inner_lambda_") { auto lambda = [pa_vector](vector_type x) { REQUIRE(x.data() == pa_vector); - return std::move(x); + return x; }; auto lambda2 = detail_::make_inner_lambda_(lambda, std::move(a_vector)); REQUIRE(lambda2().data() == pa_vector); @@ -249,7 +249,7 @@ TEST_CASE("make_outer_lambda_") { SECTION("Returns") { auto lambda = [pa_vector](vector_type x) { REQUIRE(x.data() == pa_vector); - return std::move(x); + return x; }; auto lambda2 = detail_::make_outer_lambda_(lambda, std::move(a_vector)); REQUIRE(std::any_cast(lambda2()).data() == pa_vector);