From 7971824914eb58c21868d9cf43c8b019b1c46418 Mon Sep 17 00:00:00 2001 From: John Wellbelove Date: Thu, 9 Apr 2026 07:21:19 +0100 Subject: [PATCH] Add the ability to specify the callback type to etl closure (#1393) * Print test names at test time (#1343) * Modified closure to accept the callback type as a template parameter * Modified closure to accept the callback type as a template parameter * Applied clang-format * Fixed C++03 compatibility * Fixed C++03 compatibility # Conflicts: # include/etl/closure.h --------- Co-authored-by: Roland Reichwein Co-authored-by: John Wellbelove --- include/etl/closure.h | 170 +++++++------- test/CMakeLists.txt | 7 +- ...=> test_closure_with_default_delegate.cpp} | 2 +- ...osure_with_default_delegate_constexpr.cpp} | 2 +- test/test_closure_with_inplace_function.cpp | 208 ++++++++++++++++++ test/vs2022/etl.vcxproj | 5 +- test/vs2022/etl.vcxproj.filters | 13 +- 7 files changed, 311 insertions(+), 96 deletions(-) rename test/{test_closure.cpp => test_closure_with_default_delegate.cpp} (99%) rename test/{test_closure_constexpr.cpp => test_closure_with_default_delegate_constexpr.cpp} (99%) create mode 100644 test/test_closure_with_inplace_function.cpp diff --git a/include/etl/closure.h b/include/etl/closure.h index 54b0fb36..2c150ec3 100644 --- a/include/etl/closure.h +++ b/include/etl/closure.h @@ -33,53 +33,62 @@ SOFTWARE. #include "platform.h" #include "delegate.h" +#include "invoke.h" #include "tuple.h" #include "type_list.h" #include "utility.h" namespace etl { -#if ETL_USING_CPP11 && !defined(ETL_CLOSURE_FORCE_CPP03_IMPLEMENTATION) //************************************************************************* /// Base template for closure. + /// \tparam TSignature The callback signature. + /// \tparam TCallback The callback type, defaults to etl::delegate. //************************************************************************* - template + template > class closure; +#if ETL_USING_CPP11 && !defined(ETL_CLOSURE_FORCE_CPP03_IMPLEMENTATION) //************************************************************************* - /// Closure for binding arguments to a delegate and invoking it later. - /// Stores a delegate and its arguments, allowing deferred execution. + /// Closure for binding arguments to a callback and invoking it later. + /// Stores a callback and its arguments, allowing deferred execution. /// Arguments are stored in a tuple and can be rebound using bind(). /// Example usage: /// \code /// etl::closure c(my_delegate, 1, 2); /// c(); // Invokes my_delegate(1, 2) /// \endcode - /// \tparam TReturn The return type of the delegate. - /// \tparam TArgs The argument types of the delegate. + /// \tparam TReturn The return type of the callback. + /// \tparam TArgs The argument types of the callback. + /// \tparam TCallback The callback type. //************************************************************************* - template - class closure + template + class closure { public: - using delegate_type = etl::delegate; ///< The delegate type to be invoked. - using argument_types = etl::type_list; ///< The type list of arguments. + ETL_DEPRECATED_REASON("Use callback_type") using delegate_type = + TCallback; ///< The callback type to be invoked. Deprecated, use callback_type instead. + using callback_type = TCallback; ///< The callback type to be invoked. + using argument_types = etl::type_list; ///< The type list of arguments. + + static_assert(etl::is_invocable_r::value, "Callback is not invocable with the specified arguments"); + static_assert(etl::is_copy_constructible::value, "Callback type must be copy constructible"); //********************************************************************* - /// Construct a closure with a delegate and its arguments. - /// \param f The delegate to be invoked. - /// \param args The arguments to bind to the delegate. + /// Construct a closure with a callback and its arguments. + /// \param f The callback to be invoked. + /// \param args The arguments to bind to the callback. //********************************************************************* - ETL_CONSTEXPR14 closure(const delegate_type& f, const TArgs... args) + ETL_CONSTEXPR14 closure(const callback_type& f, const TArgs... args) : m_f(f) , m_args(args...) { } //********************************************************************* - /// Invoke the stored delegate with the bound arguments. - /// \return The result of the delegate invocation. + /// Invoke the stored callback with the bound arguments. + /// \return The result of the callback invocation. //********************************************************************* ETL_CONSTEXPR14 TReturn operator()() const { @@ -131,7 +140,7 @@ namespace etl //********************************************************************* /// Execute the closure with the stored arguments. /// \tparam idx Index sequence for tuple unpacking. - /// \return The result of the delegate invocation. + /// \return The result of the callback invocation. //********************************************************************* template ETL_CONSTEXPR14 TReturn execute(etl::index_sequence) const @@ -139,43 +148,38 @@ namespace etl return m_f(etl::get(m_args)...); } - delegate_type m_f; ///< The delegate to invoke. + callback_type m_f; ///< The callback to invoke. etl::tuple m_args; ///< The bound arguments. }; #else //************************************************************************* - /// Base template for closure. - //************************************************************************* - template - class closure; - - //************************************************************************* - /// Closure for binding one argument to a delegate and invoking it later. - /// \tparam TReturn The return type of the delegate. + /// Closure for binding one argument to a callback and invoking it later. + /// \tparam TReturn The return type of the callback. /// \tparam TArg0 The type of the argument. + /// \tparam TCallback The callback type. //************************************************************************* - template - class closure + template + class closure { public: - /// The delegate type to be invoked. - typedef etl::delegate delegate_type; + /// The callback type to be invoked. + typedef TCallback callback_type; //********************************************************************* - /// Construct a closure with a delegate and its argument. - /// \param f The delegate to be invoked. - /// \param arg0 The argument to bind to the delegate. + /// Construct a closure with a callback and its argument. + /// \param f The callback to be invoked. + /// \param arg0 The argument to bind to the callback. //********************************************************************* - closure(const delegate_type& f, const TArg0 arg0) + closure(const callback_type& f, const TArg0 arg0) : m_f(f) , m_arg0(arg0) { } //********************************************************************* - /// Invoke the stored delegate with the bound argument. - /// \return The result of the delegate invocation. + /// Invoke the stored callback with the bound argument. + /// \return The result of the callback invocation. //********************************************************************* TReturn operator()() const { @@ -184,30 +188,31 @@ namespace etl private: - delegate_type m_f; ///< The delegate to invoke. + callback_type m_f; ///< The callback to invoke. TArg0 m_arg0; }; //************************************************************************* - /// Closure for binding two arguments to a delegate and invoking it later. - /// \tparam TReturn The return type of the delegate. + /// Closure for binding two arguments to a callback and invoking it later. + /// \tparam TReturn The return type of the callback. /// \tparam TArg0 The type of the first argument. /// \tparam TArg1 The type of the second argument. + /// \tparam TCallback The callback type. //************************************************************************* - template - class closure + template + class closure { public: - typedef etl::delegate delegate_type; + typedef TCallback callback_type; //********************************************************************* - /// Construct a closure with a delegate and its arguments. - /// \param f The delegate to be invoked. + /// Construct a closure with a callback and its arguments. + /// \param f The callback to be invoked. /// \param arg0 The first argument to bind. /// \param arg1 The second argument to bind. //********************************************************************* - closure(const delegate_type& f, const TArg0 arg0, const TArg1 arg1) + closure(const callback_type& f, const TArg0 arg0, const TArg1 arg1) : m_f(f) , m_arg0(arg0) , m_arg1(arg1) @@ -215,8 +220,8 @@ namespace etl } //********************************************************************* - /// Invoke the stored delegate with the bound arguments. - /// \return The result of the delegate invocation. + /// Invoke the stored callback with the bound arguments. + /// \return The result of the callback invocation. //********************************************************************* TReturn operator()() const { @@ -225,33 +230,34 @@ namespace etl private: - delegate_type m_f; ///< The delegate to invoke. + callback_type m_f; ///< The callback to invoke. TArg0 m_arg0; TArg1 m_arg1; }; //************************************************************************* - /// Closure for binding three arguments to a delegate and invoking it later. - /// \tparam TReturn The return type of the delegate. + /// Closure for binding three arguments to a callback and invoking it later. + /// \tparam TReturn The return type of the callback. /// \tparam TArg0 The type of the first argument. /// \tparam TArg1 The type of the second argument. /// \tparam TArg2 The type of the third argument. + /// \tparam TCallback The callback type. //************************************************************************* - template - class closure + template + class closure { public: - typedef etl::delegate delegate_type; + typedef TCallback callback_type; //********************************************************************* - /// Construct a closure with a delegate and its arguments. - /// \param f The delegate to be invoked. + /// Construct a closure with a callback and its arguments. + /// \param f The callback to be invoked. /// \param arg0 The first argument to bind. /// \param arg1 The second argument to bind. /// \param arg2 The third argument to bind. //********************************************************************* - closure(const delegate_type& f, const TArg0 arg0, const TArg1 arg1, const TArg2 arg2) + closure(const callback_type& f, const TArg0 arg0, const TArg1 arg1, const TArg2 arg2) : m_f(f) , m_arg0(arg0) , m_arg1(arg1) @@ -260,8 +266,8 @@ namespace etl } //********************************************************************* - /// Invoke the stored delegate with the bound arguments. - /// \return The result of the delegate invocation. + /// Invoke the stored callback with the bound arguments. + /// \return The result of the callback invocation. //********************************************************************* TReturn operator()() const { @@ -270,36 +276,37 @@ namespace etl private: - delegate_type m_f; ///< The delegate to invoke. + callback_type m_f; ///< The callback to invoke. TArg0 m_arg0; TArg1 m_arg1; TArg2 m_arg2; }; //************************************************************************* - /// Closure for binding four arguments to a delegate and invoking it later. - /// \tparam TReturn The return type of the delegate. + /// Closure for binding four arguments to a callback and invoking it later. + /// \tparam TReturn The return type of the callback. /// \tparam TArg0 The type of the first argument. /// \tparam TArg1 The type of the second argument. /// \tparam TArg2 The type of the third argument. /// \tparam TArg3 The type of the fourth argument. + /// \tparam TCallback The callback type. //************************************************************************* - template - class closure + template + class closure { public: - typedef etl::delegate delegate_type; + typedef TCallback callback_type; //********************************************************************* - /// Construct a closure with a delegate and its arguments. - /// \param f The delegate to be invoked. + /// Construct a closure with a callback and its arguments. + /// \param f The callback to be invoked. /// \param arg0 The first argument to bind. /// \param arg1 The second argument to bind. /// \param arg2 The third argument to bind. /// \param arg3 The fourth argument to bind. //********************************************************************* - closure(const delegate_type& f, const TArg0 arg0, const TArg1 arg1, const TArg2 arg2, const TArg3 arg3) + closure(const callback_type& f, const TArg0 arg0, const TArg1 arg1, const TArg2 arg2, const TArg3 arg3) : m_f(f) , m_arg0(arg0) , m_arg1(arg1) @@ -309,8 +316,8 @@ namespace etl } //********************************************************************* - /// Invoke the stored delegate with the bound arguments. - /// \return The result of the delegate invocation. + /// Invoke the stored callback with the bound arguments. + /// \return The result of the callback invocation. //********************************************************************* TReturn operator()() const { @@ -319,7 +326,7 @@ namespace etl private: - delegate_type m_f; ///< The delegate to invoke. + callback_type m_f; ///< The callback to invoke. TArg0 m_arg0; TArg1 m_arg1; TArg2 m_arg2; @@ -327,31 +334,32 @@ namespace etl }; //************************************************************************* - /// Closure for binding five arguments to a delegate and invoking it later. - /// \tparam TReturn The return type of the delegate. + /// Closure for binding five arguments to a callback and invoking it later. + /// \tparam TReturn The return type of the callback. /// \tparam TArg0 The type of the first argument. /// \tparam TArg1 The type of the second argument. /// \tparam TArg2 The type of the third argument. /// \tparam TArg3 The type of the fourth argument. /// \tparam TArg4 The type of the fifth argument. + /// \tparam TCallback The callback type. //************************************************************************* - template - class closure + template + class closure { public: - typedef etl::delegate delegate_type; + typedef TCallback callback_type; //********************************************************************* - /// Construct a closure with a delegate and its arguments. - /// \param f The delegate to be invoked. + /// Construct a closure with a callback and its arguments. + /// \param f The callback to be invoked. /// \param arg0 The first argument to bind. /// \param arg1 The second argument to bind. /// \param arg2 The third argument to bind. /// \param arg3 The fourth argument to bind. /// \param arg4 The fifth argument to bind. //********************************************************************* - closure(const delegate_type& f, const TArg0 arg0, const TArg1 arg1, const TArg2 arg2, const TArg3 arg3, const TArg4 arg4) + closure(const callback_type& f, const TArg0 arg0, const TArg1 arg1, const TArg2 arg2, const TArg3 arg3, const TArg4 arg4) : m_f(f) , m_arg0(arg0) , m_arg1(arg1) @@ -362,8 +370,8 @@ namespace etl } //********************************************************************* - /// Invoke the stored delegate with the bound arguments. - /// \return The result of the delegate invocation. + /// Invoke the stored callback with the bound arguments. + /// \return The result of the callback invocation. //********************************************************************* TReturn operator()() const { @@ -372,7 +380,7 @@ namespace etl private: - delegate_type m_f; ///< The delegate to invoke. + callback_type m_f; ///< The callback to invoke. TArg0 m_arg0; TArg1 m_arg1; TArg2 m_arg2; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a559add4..9f325bb9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -83,8 +83,9 @@ add_executable(etl_tests test_circular_buffer.cpp test_circular_buffer_external_buffer.cpp test_circular_iterator.cpp - test_closure.cpp - test_closure_constexpr.cpp + test_closure_with_default_delegate.cpp + test_closure_with_default_delegate_constexpr.cpp + test_closure_with_inplace_function.cpp test_compare.cpp test_concepts.cpp test_constant.cpp @@ -523,7 +524,7 @@ if ((CMAKE_CXX_COMPILER_ID MATCHES "GNU") OR (CMAKE_CXX_COMPILER_ID MATCHES "Cla target_link_options(etl_tests PRIVATE -fsanitize=address,undefined,bounds - ) + ) endif() endif () endif () diff --git a/test/test_closure.cpp b/test/test_closure_with_default_delegate.cpp similarity index 99% rename from test/test_closure.cpp rename to test/test_closure_with_default_delegate.cpp index 3e01ca8b..a7ef4f35 100644 --- a/test/test_closure.cpp +++ b/test/test_closure_with_default_delegate.cpp @@ -34,7 +34,7 @@ SOFTWARE. namespace { - SUITE(test_closure) + SUITE(test_closure_with_default_delegate) { int f1(int a1) { diff --git a/test/test_closure_constexpr.cpp b/test/test_closure_with_default_delegate_constexpr.cpp similarity index 99% rename from test/test_closure_constexpr.cpp rename to test/test_closure_with_default_delegate_constexpr.cpp index 73c4b68c..57dcc18d 100644 --- a/test/test_closure_constexpr.cpp +++ b/test/test_closure_with_default_delegate_constexpr.cpp @@ -34,7 +34,7 @@ SOFTWARE. namespace { - SUITE(test_closure) + SUITE(test_closure_with_default_delegate_constexpr) { static constexpr int f1(int a1) { diff --git a/test/test_closure_with_inplace_function.cpp b/test/test_closure_with_inplace_function.cpp new file mode 100644 index 00000000..cd67ffba --- /dev/null +++ b/test/test_closure_with_inplace_function.cpp @@ -0,0 +1,208 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2025 BMW AG, John Wellbelove + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#include "unit_test_framework.h" + +#include "etl/closure.h" +#include "etl/inplace_function.h" + +#include + +namespace +{ + SUITE(test_closure_with_inplace_function) + { + int f1(int a1) + { + return a1 * 3; + } + + int f1_throwing(int) + { + throw std::runtime_error("throwing function"); + } + + void f1_void(int) {} + + int f1_ref(int& a1) + { + return a1 * 3; + } + + int f2(int a1, int a2) + { + return a1 * 3 + a2; + } + + int f3(int a1, int a2, int a3) + { + return a1 * 3 + a2 * a3; + } + + int f4(int a1, int a2, int a3, int a4) + { + return a1 * 3 + a2 * a3 + a4; + } + + int f5(int a1, int a2, int a3, int a4, int a5) + { + return a1 * 3 + a2 * a3 + a4 * a5; + } + + using f1_type = int(int); + using f1_ref_type = int(int&); + using f1_void_type = void(int); + using f2_type = int(int, int); + using f3_type = int(int, int, int); + using f4_type = int(int, int, int, int); + using f5_type = int(int, int, int, int, int); + + using ipf1_type = etl::inplace_function; + using ipf1_ref_type = etl::inplace_function; + using ipf1_void_type = etl::inplace_function; + using ipf2_type = etl::inplace_function; + using ipf3_type = etl::inplace_function; + using ipf4_type = etl::inplace_function; + using ipf5_type = etl::inplace_function; + + ipf1_type ipf1 = ipf1_type::create<&f1>(); + ipf1_type ipf1_throwing = ipf1_type::create<&f1_throwing>(); + ipf1_ref_type ipf1_ref = ipf1_ref_type::create<&f1_ref>(); + ipf1_void_type ipf1_void = ipf1_void_type::create<&f1_void>(); + ipf2_type ipf2 = ipf2_type::create<&f2>(); + ipf3_type ipf3 = ipf3_type::create<&f3>(); + ipf4_type ipf4 = ipf4_type::create<&f4>(); + ipf5_type ipf5 = ipf5_type::create<&f5>(); + + //************************************************************************* + TEST(test_1_arg) + { + etl::closure c1(ipf1, 4); + CHECK_EQUAL(12, c1()); + } + + //************************************************************************* + TEST(test_1_arg_reference) + { + int v1 = 4; + etl::closure c1_ref(ipf1_ref, v1); + CHECK_EQUAL(12, c1_ref()); + v1 = 5; + CHECK_EQUAL(15, c1_ref()); + } + + //************************************************************************* + TEST(test_1_arg_lambda) + { + auto l = [](int a) + { + return a + 11; + }; + ipf1_type ipf1_lambda(l); + + etl::closure c1_lambda(ipf1_lambda, 5); + CHECK_EQUAL(16, c1_lambda()); + } + + //************************************************************************* + TEST(test_throwing) + { + etl::closure c1(ipf1_throwing, 4); + CHECK_THROW(c1(), std::runtime_error); + } + + //************************************************************************* + TEST(test_void) + { + etl::closure c1(ipf1_void, 4); + c1(); + } + + //************************************************************************* + TEST(test_2_args) + { + etl::closure c2(ipf2, 4, 3); + CHECK_EQUAL(15, c2()); + } + +#if ETL_USING_CPP11 && !defined(ETL_CLOSURE_FORCE_CPP03_IMPLEMENTATION) + //************************************************************************* + TEST(test_2_args_bind) + { + etl::closure c2(ipf2, 4, 3); + CHECK_EQUAL(15, c2()); + + c2.bind<0>(7); + c2.bind<1>(8); + CHECK_EQUAL(29, c2()); + } + + //************************************************************************* + TEST(test_2_args_bind_all) + { + etl::closure c2(ipf2, 4, 3); + CHECK_EQUAL(15, c2()); + + c2.bind(7, 8); + CHECK_EQUAL(29, c2()); + } +#endif + + //************************************************************************* + TEST(test_3_args) + { + etl::closure c3(ipf3, 4, 3, 2); + CHECK_EQUAL(18, c3()); + } + + //************************************************************************* + TEST(test_4_args) + { + etl::closure c4(ipf4, 4, 3, 2, 1); + CHECK_EQUAL(19, c4()); + } + + //************************************************************************* + TEST(test_5_args) + { + etl::closure c5(ipf5, 4, 3, 2, 1, 5); + CHECK_EQUAL(23, c5()); + } + + //************************************************************************* + TEST(test_bind_static_assert) + { + etl::closure c(ipf2, 1, 2); + + // Uncomment to generate static_assert errors. + // c.bind(1); // Argument count mismatch + // c.bind(1, 2, 3); // Argument count mismatch + // c.bind(1, std::string()); // Argument is not convertible + } + } +} // namespace diff --git a/test/vs2022/etl.vcxproj b/test/vs2022/etl.vcxproj index 93ea62c4..0e0b6601 100644 --- a/test/vs2022/etl.vcxproj +++ b/test/vs2022/etl.vcxproj @@ -10273,8 +10273,9 @@ - - + + + diff --git a/test/vs2022/etl.vcxproj.filters b/test/vs2022/etl.vcxproj.filters index 4921e669..8553316d 100644 --- a/test/vs2022/etl.vcxproj.filters +++ b/test/vs2022/etl.vcxproj.filters @@ -3647,7 +3647,7 @@ Tests\Chrono - + Tests\Callbacks & Delegates @@ -3704,7 +3704,7 @@ Tests\CRC - + Tests\Callbacks & Delegates @@ -3800,6 +3800,9 @@ Tests\Strings + + Tests\Callbacks & Delegates + @@ -3979,12 +3982,6 @@ Resource Files\CI\Github - - Documentation - - - Documentation -