From 6d6ecc9fb5f35c28e0caad63d121e5077d8599b7 Mon Sep 17 00:00:00 2001 From: John Wellbelove Date: Sat, 10 Jan 2026 09:46:50 +0100 Subject: [PATCH] Add etl::inplace_function (#1251) * Imported inplace_function and invoke functionality from original branch * Fixed spelling mistake * Update test/CMakeLists.txt Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update test/test_inplace_function.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Moved member type function_ptr to private section * Updated comments in inplace_function.h * Updated action workflows to be triggered on a pull-request based on development branch * Added suggested changes from PR reviews --------- Co-authored-by: John Wellbelove Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: John Wellbelove --- .github/workflows/clang-c++11.yml | 2 +- .github/workflows/clang-c++14.yml | 2 +- .github/workflows/clang-c++17.yml | 2 +- .github/workflows/clang-c++20.yml | 2 +- .github/workflows/clang-c++23.yml | 3 +- .github/workflows/clang-syntax-checks.yml | 2 +- .github/workflows/gcc-c++11.yml | 2 +- .github/workflows/gcc-c++14.yml | 2 +- .github/workflows/gcc-c++17.yml | 2 +- .github/workflows/gcc-c++20.yml | 2 +- .github/workflows/gcc-c++23.yml | 3 +- .github/workflows/gcc-syntax-checks.yml | 2 +- .github/workflows/msvc.yml | 2 +- include/etl/file_error_numbers.h | 1 + include/etl/inplace_function.h | 1529 +++++++++++++++++ include/etl/invoke.h | 58 +- test/CMakeLists.txt | 6 +- test/syntax_check/CMakeLists.txt | 2 + test/syntax_check/inplace_function.h.t.cpp | 29 + test/syntax_check/invoke.h.t.cpp | 29 + test/test_inplace_function.cpp | 1780 ++++++++++++++++++++ test/test_invoke.cpp | 532 ++++-- test/test_is_invocable.cpp | 1011 +++++++++++ test/vs2022/etl.vcxproj | 3 + test/vs2022/etl.vcxproj.filters | 11 +- 25 files changed, 4886 insertions(+), 133 deletions(-) create mode 100644 include/etl/inplace_function.h create mode 100644 test/syntax_check/inplace_function.h.t.cpp create mode 100644 test/syntax_check/invoke.h.t.cpp create mode 100644 test/test_inplace_function.cpp create mode 100644 test/test_is_invocable.cpp diff --git a/.github/workflows/clang-c++11.yml b/.github/workflows/clang-c++11.yml index 610b4529..1d6976eb 100644 --- a/.github/workflows/clang-c++11.yml +++ b/.github/workflows/clang-c++11.yml @@ -3,7 +3,7 @@ on: push: branches: [ master, development, pull-request/* ] pull_request: - branches: [ master, pull-request/* ] + branches: [ master, development, pull-request/* ] types: [opened, synchronize, reopened] jobs: diff --git a/.github/workflows/clang-c++14.yml b/.github/workflows/clang-c++14.yml index 233eabff..dd6c2aad 100644 --- a/.github/workflows/clang-c++14.yml +++ b/.github/workflows/clang-c++14.yml @@ -3,7 +3,7 @@ on: push: branches: [ master, development, pull-request/* ] pull_request: - branches: [ master, pull-request/* ] + branches: [ master, development, pull-request/* ] types: [opened, synchronize, reopened] jobs: diff --git a/.github/workflows/clang-c++17.yml b/.github/workflows/clang-c++17.yml index 2c659fc6..bc54989d 100644 --- a/.github/workflows/clang-c++17.yml +++ b/.github/workflows/clang-c++17.yml @@ -3,7 +3,7 @@ on: push: branches: [ master, development, pull-request/* ] pull_request: - branches: [ master, pull-request/* ] + branches: [ master, development, pull-request/* ] types: [opened, synchronize, reopened] jobs: diff --git a/.github/workflows/clang-c++20.yml b/.github/workflows/clang-c++20.yml index d170a982..bfd88665 100644 --- a/.github/workflows/clang-c++20.yml +++ b/.github/workflows/clang-c++20.yml @@ -3,7 +3,7 @@ on: push: branches: [ master, development, pull-request/* ] pull_request: - branches: [ master, pull-request/* ] + branches: [ master, development, pull-request/* ] types: [opened, synchronize, reopened] jobs: diff --git a/.github/workflows/clang-c++23.yml b/.github/workflows/clang-c++23.yml index b6b6cd84..3e1f5a67 100644 --- a/.github/workflows/clang-c++23.yml +++ b/.github/workflows/clang-c++23.yml @@ -3,7 +3,8 @@ on: push: branches: [ master, development, pull-request/* ] pull_request: - branches: [ master, pull-request/* ] + branches: [ master, development, pull-request/* ] + types: [opened, synchronize, reopened] jobs: diff --git a/.github/workflows/clang-syntax-checks.yml b/.github/workflows/clang-syntax-checks.yml index 0b32ecb2..700b2087 100644 --- a/.github/workflows/clang-syntax-checks.yml +++ b/.github/workflows/clang-syntax-checks.yml @@ -3,7 +3,7 @@ on: push: branches: [ master, development, pull-request/* ] pull_request: - branches: [ master, pull-request/* ] + branches: [ master, development, pull-request/* ] types: [opened, synchronize, reopened] jobs: diff --git a/.github/workflows/gcc-c++11.yml b/.github/workflows/gcc-c++11.yml index 100d179c..938402c1 100644 --- a/.github/workflows/gcc-c++11.yml +++ b/.github/workflows/gcc-c++11.yml @@ -3,7 +3,7 @@ on: push: branches: [ master, development, pull-request/* ] pull_request: - branches: [ master, pull-request/* ] + branches: [ master, development, pull-request/* ] types: [opened, synchronize, reopened] jobs: diff --git a/.github/workflows/gcc-c++14.yml b/.github/workflows/gcc-c++14.yml index 881f4607..59a06efc 100644 --- a/.github/workflows/gcc-c++14.yml +++ b/.github/workflows/gcc-c++14.yml @@ -3,7 +3,7 @@ on: push: branches: [ master, development, pull-request/* ] pull_request: - branches: [ master, pull-request/* ] + branches: [ master, development, pull-request/* ] types: [opened, synchronize, reopened] jobs: diff --git a/.github/workflows/gcc-c++17.yml b/.github/workflows/gcc-c++17.yml index 931c9a6d..90f6ced5 100644 --- a/.github/workflows/gcc-c++17.yml +++ b/.github/workflows/gcc-c++17.yml @@ -3,7 +3,7 @@ on: push: branches: [ master, development, pull-request/* ] pull_request: - branches: [ master, pull-request/* ] + branches: [ master, development, pull-request/* ] types: [opened, synchronize, reopened] jobs: diff --git a/.github/workflows/gcc-c++20.yml b/.github/workflows/gcc-c++20.yml index 9240046a..62833fbf 100644 --- a/.github/workflows/gcc-c++20.yml +++ b/.github/workflows/gcc-c++20.yml @@ -3,7 +3,7 @@ on: push: branches: [ master, development, pull-request/* ] pull_request: - branches: [ master, pull-request/* ] + branches: [ master, development, pull-request/* ] types: [opened, synchronize, reopened] jobs: diff --git a/.github/workflows/gcc-c++23.yml b/.github/workflows/gcc-c++23.yml index 93881f84..4b2efa3d 100644 --- a/.github/workflows/gcc-c++23.yml +++ b/.github/workflows/gcc-c++23.yml @@ -3,7 +3,8 @@ on: push: branches: [ master, development, pull-request/* ] pull_request: - branches: [ master, pull-request/* ] + branches: [ master, development, pull-request/* ] + types: [opened, synchronize, reopened] jobs: diff --git a/.github/workflows/gcc-syntax-checks.yml b/.github/workflows/gcc-syntax-checks.yml index 6db13f7b..5f3753b1 100644 --- a/.github/workflows/gcc-syntax-checks.yml +++ b/.github/workflows/gcc-syntax-checks.yml @@ -3,7 +3,7 @@ on: push: branches: [ master, development, pull-request/* ] pull_request: - branches: [ master, pull-request/* ] + branches: [ master, development, pull-request/* ] types: [opened, synchronize, reopened] jobs: diff --git a/.github/workflows/msvc.yml b/.github/workflows/msvc.yml index 08180a9e..9f28f025 100644 --- a/.github/workflows/msvc.yml +++ b/.github/workflows/msvc.yml @@ -3,7 +3,7 @@ on: push: branches: [ master, development, pull-request/* ] pull_request: - branches: [ master, pull-request/* ] + branches: [ master, development, pull-request/* ] types: [opened, synchronize, reopened] jobs: diff --git a/include/etl/file_error_numbers.h b/include/etl/file_error_numbers.h index b3e08814..47af25c0 100644 --- a/include/etl/file_error_numbers.h +++ b/include/etl/file_error_numbers.h @@ -110,4 +110,5 @@ SOFTWARE. #define ETL_NOT_NULL_FILE_ID "77" #define ETL_SIGNAL_FILE_ID "78" #define ETL_FORMAT_FILE_ID "79" +#define ETL_INPLACE_FUNCTION_FILE_ID "80" #endif diff --git a/include/etl/inplace_function.h b/include/etl/inplace_function.h new file mode 100644 index 00000000..a027f342 --- /dev/null +++ b/include/etl/inplace_function.h @@ -0,0 +1,1529 @@ +///\file + +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2025 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. +******************************************************************************/ + +#ifndef ETL_INPLACE_FUNCTION_INCLUDED +#define ETL_INPLACE_FUNCTION_INCLUDED + +#include "platform.h" + +#if ETL_USING_CPP11 + +#include "utility.h" +#include "optional.h" +#include "placement_new.h" +#include "type_traits.h" +#include "function_traits.h" +#include "error_handler.h" +#include "exception.h" +#include "invoke.h" +#include "file_error_numbers.h" + +#if !defined(ETL_DEFAULT_INPLACE_FUNCTION_SIZE) + #define ETL_DEFAULT_INPLACE_FUNCTION_SIZE 32 +#endif + +#if !defined(ETL_DEFAULT_INPLACE_FUNCTION_ALIGNMENT) + #define ETL_DEFAULT_INPLACE_FUNCTION_ALIGNMENT alignof(void*) +#endif + +namespace etl +{ + //************************************************************************* + /// Exception base for inplace_function + //************************************************************************* + class inplace_function_exception : public etl::exception + { + public: + + inplace_function_exception(string_type reason_, string_type file_name_, numeric_type line_number_) + : exception(reason_, file_name_, line_number_) + { + } + }; + + //************************************************************************* + /// Inplace function uninitialized exception. + //************************************************************************* + class inplace_function_uninitialized : public inplace_function_exception + { + public: + + inplace_function_uninitialized(string_type file_name_, numeric_type line_number_) + : inplace_function_exception(ETL_ERROR_TEXT("inplace_function:uninitialized", ETL_INPLACE_FUNCTION_FILE_ID"A"), file_name_, line_number_) + { + } + }; + + namespace private_inplace_function + { + //************************************************************************* + /// The vtable for inplace_function + //************************************************************************* + template + struct inplace_function_vtable + { + using invoke_type = TReturn(*)(void*, TArgs...); + using destroy_type = void(*)(void*); + using move_type = void(*)(void*, void*); + using copy_type = void(*)(const void*, void*); + + invoke_type invoke = nullptr; + destroy_type destroy = nullptr; + move_type move = nullptr; + copy_type copy = nullptr; + + //***************************************** + // Constructor + //***************************************** + ETL_CONSTEXPR inplace_function_vtable(invoke_type i, + destroy_type d, + move_type m, + copy_type c) + : invoke(i) + , destroy(d) + , move(m) + , copy(c) {} + + //***************************************** + // Payload records for member bindings + //***************************************** + // Non-const member function + //***************************************** + template + struct member_target + { + TReturn (TObject::* member)(TArgs...); + TObject obj; + }; + + //***************************************** + // Const member function + //***************************************** + template + struct const_member_target + { + TReturn(TObject::* member)(TArgs...) const; + TObject obj; + }; + + //***************************************** + // Storage helpers + //***************************************** + // Copy construct + //***************************************** + template + static void copy_construct(const void* src, void* dst) + { + ::new (dst) T(*static_cast(src)); + } + + //***************************************** + // Move construct + //***************************************** + template + static void move_construct(void* src, void* dst) + { + ::new (dst) T(etl::move(*static_cast(src))); + + if (DestroySrc) + { + static_cast(src)->~T(); + } + } + + //***************************************** + // Stub functions + //***************************************** + // Destroy function + //***************************************** + template + static void destroy_stub(void* p) + { + static_cast(p)->~T(); + } + + //***************************************** + // Free function - Returning value + //***************************************** + template ::value, int> = 0> + static R stub_function_ptr(void* p, TArgs... a) + { + return (*static_cast(p))(etl::forward(a)...); + } + + //***************************************** + // Free function - returning void + //***************************************** + template ::value, int> = 0> + static void stub_function_ptr(void* p, TArgs... a) + { + (*static_cast(p))(etl::forward(a)...); + } + + //***************************************** + // Member target - Returning value + //***************************************** + template ::value, int> = 0> + static R stub_member(void* p, TArgs... a) + { + auto* s = static_cast(p); + return (s->obj.*(s->member))(etl::forward(a)...); + } + + //***************************************** + // Member target - returning void + //***************************************** + template ::value, int> = 0> + static void stub_member(void* p, TArgs... a) + { + auto* s = static_cast(p); + (s->obj.*(s->member))(etl::forward(a)...); + } + + //***************************************** + // Functor / lambda stored in payload - returning value + //***************************************** + template ::value, int> = 0> + static R stub_functor(void* p, TArgs... a) + { + return (*static_cast(p))(etl::forward(a)...); + } + + //***************************************** + // Functor / lambda stored in payload - returning void + //***************************************** + template ::value, int> = 0> + static void stub_functor(void* p, TArgs... a) + { + (*static_cast(p))(etl::forward(a)...); + } + + //***************************************** + // Const functor / lambda stored in payload - returning value + //***************************************** + template ::value, int> = 0> + static R stub_const_functor(void* p, TArgs... a) + { + return (*static_cast(p))(etl::forward(a)...); + } + + //***************************************** + // Const functor / lambda stored in payload - returning void + //***************************************** + template ::value, int> = 0> + static void stub_const_functor(void* p, TArgs... a) + { + (*static_cast(p))(etl::forward(a)...); + } + + //***************************************** + // Compile-time bound free function - returning value + //***************************************** + template ::value, int> = 0> + static R stub_ct_function(void*, TArgs... a) + { + return Function(etl::forward(a)...); + } + + //***************************************** + // Compile-time bound free function - returning void + //***************************************** + template ::value, int> = 0> + static void stub_ct_function(void*, TArgs... a) + { + Function(etl::forward(a)...); + } + + //***************************************** + // Compile-time bound member + object - returning value + //***************************************** + template ::value, int> = 0> + static R stub_ct_member(void*, TArgs... a) + { + return (Object->*Method)(etl::forward(a)...); + } + + //***************************************** + // Compile-time bound member + object - returning void + //***************************************** + template ::value, int> = 0> + static void stub_ct_member(void*, TArgs... a) + { + (Object->*Method)(etl::forward(a)...); + } + + //***************************************** + // Compile-time bound const member + object - returning value + //***************************************** + template ::value, int> = 0> + static R stub_ct_const_member(void*, TArgs... a) + { + return (Object->*Method)(etl::forward(a)...); + } + + //***************************************** + // Compile-time bound const member + object - returning void + //***************************************** + template ::value, int> = 0> + static void stub_ct_const_member(void*, TArgs... a) + { + (Object->*Method)(etl::forward(a)...); + } + + //***************************************** + // Compile-time bound operator() + object - returning value + //***************************************** + template ::value, int> = 0> + static R stub_ct_operator(void*, TArgs... a) + { + return (*Object)(etl::forward(a)...); + } + + //***************************************** + // Compile-time bound operator() + object - returning void + //***************************************** + template ::value, int> = 0> + static void stub_ct_operator(void*, TArgs... a) + { + (*Object)(etl::forward(a)...); + } + + //***************************************** + // vtable factories + //***************************************** + // Free function pointer + //***************************************** + static const inplace_function_vtable* for_function_ptr() + { + using function_type = TReturn(*)(TArgs...); + static const inplace_function_vtable vtable(&inplace_function_vtable::template stub_function_ptr, + nullptr, + &inplace_function_vtable::template move_construct, + &inplace_function_vtable::template copy_construct); + return &vtable; + } + + //***************************************** + // Member function pointer + //***************************************** + template + static const inplace_function_vtable* for_member() + { + using target_t = member_target; + constexpr bool destroy_src_on_move = !etl::is_trivially_destructible::value; + + constexpr destroy_type destroy_ptr = etl::is_trivially_destructible::value ? nullptr + : &inplace_function_vtable::template destroy_stub; + + static const inplace_function_vtable vtable(&inplace_function_vtable::template stub_member, + destroy_ptr, + &inplace_function_vtable::template move_construct, + &inplace_function_vtable::template copy_construct); + return &vtable; + } + + //***************************************** + // Const member function pointer + //***************************************** + template + static const inplace_function_vtable* for_const_member() + { + using target_t = const_member_target; + constexpr bool destroy_src_on_move = !etl::is_trivially_destructible::value; + + constexpr destroy_type destroy_ptr = etl::is_trivially_destructible::value ? nullptr + : &inplace_function_vtable::template destroy_stub; + + static const inplace_function_vtable vtable(&inplace_function_vtable::template stub_member, + destroy_ptr, + &inplace_function_vtable::template move_construct, + &inplace_function_vtable::template copy_construct); + return &vtable; + } + + //***************************************** + // Functor / lambda + //***************************************** + template + static const inplace_function_vtable* for_functor() + { + constexpr bool destroy_src_on_move = !etl::is_trivially_destructible::value; + + constexpr destroy_type destroy_ptr = etl::is_trivially_destructible::value ? nullptr + : &inplace_function_vtable::template destroy_stub; + + static const inplace_function_vtable vtable(&inplace_function_vtable::template stub_functor, + destroy_ptr, + &inplace_function_vtable::template move_construct, + &inplace_function_vtable::template copy_construct); + return &vtable; + } + + //***************************************** + // Const functor / lambda + //***************************************** + template + static const inplace_function_vtable* for_const_functor() + { + constexpr bool destroy_src_on_move = !etl::is_trivially_destructible::value; + + constexpr destroy_type destroy_ptr = etl::is_trivially_destructible::value ? nullptr + : &inplace_function_vtable::template destroy_stub; + + static const inplace_function_vtable vtable(&inplace_function_vtable::template stub_const_functor, + destroy_ptr, + &inplace_function_vtable::template move_construct, + &inplace_function_vtable::template copy_construct); + return &vtable; + } + + //***************************************** + // Compile-time bound free function + //***************************************** + template + static const inplace_function_vtable* for_compile_time_function() + { + static const inplace_function_vtable vtable(&inplace_function_vtable::template stub_ct_function, + nullptr, + nullptr, + nullptr); + return &vtable; + } + + //***************************************** + // Compile-time bound member function + object + //***************************************** + template + static const inplace_function_vtable* for_compile_time_member() + { + static const inplace_function_vtable vtable(&inplace_function_vtable::template stub_ct_member, + nullptr, + nullptr, + nullptr); + return &vtable; + } + + //***************************************** + // Compile-time bound const member function + object + //***************************************** + template + static const inplace_function_vtable* for_compile_time_const_member() + { + static const inplace_function_vtable vtable(&inplace_function_vtable::template stub_ct_const_member, + nullptr, + nullptr, + nullptr); + return &vtable; + } + + //***************************************** + // Compile-time bound operator() + object + //***************************************** + template + static const inplace_function_vtable* for_compile_time_operator() + { + static const inplace_function_vtable vtable(&inplace_function_vtable::template stub_ct_operator, + nullptr, + nullptr, + nullptr); + return &vtable; + } + }; + } + + //************************************************************************* + /// inplace_function + //************************************************************************* + template + class inplace_function; + + //************************************************************************* + // Is not an etl::inplace_function + //************************************************************************* + template + struct is_inplace_function : etl::false_type {}; + + //************************************************************************* + // Is an etl::inplace_function + //************************************************************************* + template + struct is_inplace_function> : etl::true_type {}; + + //************************************************************************* + /// inplace_function + /// A function wrapper with in-place storage for the callable object. + /// \tparam TReturn The return type of the function signature. + /// \tparam TArgs The argument types of the function signature. + /// \tparam Object_Size The size of the in-place storage for the callable object. + /// \tparam Object_Alignment The alignment of the in-place storage for the callable object. + //************************************************************************* + template + class inplace_function + { + private: + + using this_type = inplace_function; + using storage_type = etl::uninitialized_buffer; + using vtable_type = private_inplace_function::inplace_function_vtable; + using function_ptr = TReturn(*)(TArgs...); + + public: + + using function_type = TReturn(TArgs...); + using return_type = TReturn; + using argument_types = etl::type_list; + + //************************************************************************* + /// Default constructor + //************************************************************************* + inplace_function() noexcept = default; + + //************************************************************************* + /// Copy constructor + ///\param other The other inplace_function to copy from. + //************************************************************************* + inplace_function(const inplace_function& other) + { + clone_from(other); + } + + //************************************************************************* + /// Copy constructor for different object buffer sizes + /// Static asserts if the destination buffer is too small. + /// Static assert if the destination alignment is too small. + /// \param other The other inplace_function to copy from. + ///************************************************************************* + template + inplace_function(const etl::inplace_function& other) + { + static_assert(Object_Size >= Other_Object_Size, "etl::inplace_function: Destination object size too small"); + static_assert(Object_Alignment >= Other_Object_Alignment, "etl::inplace_function: Destination object alignment too small"); + + clone_from(other); + } + + //************************************************************************* + /// Move constructor + /// \param other The other inplace_function to move from. + //************************************************************************* + inplace_function(inplace_function&& other) noexcept + { + move_from(other); + } + + //************************************************************************* + /// Move constructor for different object buffer + /// Static asserts if the destination buffer is too small. + /// Static assert if the destination alignment is too small. + /// \param other The other inplace_function to move from. + //************************************************************************* + template + inplace_function(etl::inplace_function&& other) noexcept + { + static_assert(Object_Size >= Other_Object_Size, "etl::inplace_function: Destination object size too small"); + static_assert(Object_Alignment >= Other_Object_Alignment, "etl::inplace_function: Destination object alignment too small"); + + move_from(other); + } + + //************************************************************************* + /// Destructor + //************************************************************************* + ~inplace_function() noexcept + { + clear(); + } + + //************************************************************************* + /// Construct from function pointer. + /// \param f The function pointer. + //************************************************************************* + inplace_function(function_ptr f) + { + set(f); + } + + //************************************************************************* + /// Construct from object + non-const member function (runtime). + /// \tparam TObject The object type. + /// \param method The member function pointer. + /// \param obj The object. + //************************************************************************* + template + inplace_function(TReturn (TObject::*method)(TArgs...), TObjectArg&& obj) + { + set(method, etl::forward(obj)); + } + + //************************************************************************* + /// Construct from object + const member function (runtime). + /// \tparam TObject The object type. + /// \param method The member function pointer. + /// \param obj The object. + //************************************************************************* + template + inplace_function(TReturn (TObject::*method)(TArgs...) const, TObjectArg&& obj) + { + set(method, etl::forward(obj)); + } + + //************************************************************************* + /// Construct from lambda/functor (runtime). + /// \tparam TLambda The lambda/functor type. + /// \param lambda The lambda/functor. + //************************************************************************* + template ::type, + typename = etl::enable_if_t::value && !is_inplace_function::value, void>> + inplace_function(TLambda& lambda) + { + set(lambda); + } + + //************************************************************************* + /// Construct from const lambda/functor (runtime). + /// \tparam TLambda The lambda/functor type. + /// \param lambda The lambda/functor. + //************************************************************************* + template ::type, + typename = etl::enable_if_t::value && !is_inplace_function::value, void>> + inplace_function(const TLambda& lambda) + { + set(lambda); + } + + //************************************************************************* + /// Create from function pointer (runtime). + /// \param f The function pointer. + //************************************************************************* + void set(function_ptr f) + { + // Validate that 'f' is invocable with (TArgs...) and returns TReturn + static_assert(etl::is_invocable_r::value, + "etl::inplace_function: function pointer is not compatible with the inplace_function signature"); + + static_assert(Object_Size >= sizeof(function_ptr), "etl::inplace_function: storage size too small"); + static_assert(Object_Alignment >= alignof(function_ptr), "etl::inplace_function: storage alignment too small"); + + clear(); + + // Construct the object in the storage. + ::new (storage_ptr()) function_ptr(f); + + vtable = vtable_type::for_function_ptr(); + } + + //************************************************************************* + /// Create from object + member function (runtime). + /// \tparam TObject The object type. + /// \param method The member function pointer. + /// \param obj The object. + //************************************************************************* + template + void set(TReturn(TObject::* method)(TArgs...), TObjectArg&& obj) + { + using D = etl::decay_t; + static_assert(etl::is_invocable_r::value, + "etl::inplace_function: bound member function is not compatible with the inplace_function signature"); + + using target_t = typename vtable_type::template member_target; + + static_assert(Object_Size >= sizeof(target_t), "etl::inplace_function: storage size too small"); + static_assert(Object_Alignment >= alignof(target_t), "etl::inplace_function: storage alignment too small"); + + clear(); + ::new (storage_ptr()) target_t{ method, etl::forward(obj) }; + vtable = vtable_type::template for_member(); + } + + //************************************************************************* + /// Create from object + const member function (runtime). + /// \tparam TObject The object type. + /// \param method The member function pointer. + /// \param obj The object. + //************************************************************************* + template + void set(TReturn(TObject::* method)(TArgs...) const, TObjectArg&& obj) + { + using D = etl::decay_t; + static_assert(etl::is_invocable_r::value, + "etl::inplace_function: bound member function is not compatible with the inplace_function signature"); + + using target_t = typename vtable_type::template const_member_target; + + static_assert(Object_Size >= sizeof(target_t), "etl::inplace_function: storage size too small"); + static_assert(Object_Alignment >= alignof(target_t), "etl::inplace_function: storage alignment too small"); + + clear(); + ::new (storage_ptr()) target_t{ method, etl::forward(obj) }; + vtable = vtable_type::template for_const_member(); + } + + //************************************************************************* + /// Create from lambda/functor (runtime). + /// \tparam TLambda The lambda/functor type. + /// \param lambda The lambda/functor. + //************************************************************************* + template ::type, + typename = etl::enable_if_t::value && !is_inplace_function::value, void>> + void set(TLambda& lambda) + { + // Validate that 'method' is invocable with (TObject&, TArgs...) and returns TReturn + static_assert(etl::is_invocable_r::value, + "etl::inplace_function: bound lambda/functor is not compatible with the inplace_function signature"); + + static_assert(Object_Size >= sizeof(T), "etl::inplace_function: Object size too small"); + static_assert(Object_Alignment >= alignof(T), "etl::inplace_function: Object alignment too small"); + + clear(); + + // Construct the object in the storage. + ::new (storage_ptr()) T(lambda); + + + vtable = vtable_type::template for_functor(); + } + + //************************************************************************* + /// Create from const lambda/functor (runtime). + /// \tparam TLambda The lambda/functor type. + /// \param lambda The lambda/functor. + //************************************************************************* + template ::type, + typename = etl::enable_if_t::value && !is_inplace_function::value, void>> + void set(const TLambda& lambda) + { + static_assert(etl::is_invocable_r::value, + "etl::inplace_function: bound lambda/functor is not compatible with the inplace_function signature"); + + static_assert(Object_Size >= sizeof(T), "etl::inplace_function: Object size too small"); + static_assert(Object_Alignment >= alignof(T), "etl::inplace_function: Object alignment too small"); + + clear(); + + // Construct the object in the storage. + ::new (storage_ptr()) T(lambda); + + vtable = vtable_type::template for_const_functor(); + } + + //************************************************************************* + /// Set from a compile-time bound free function. + /// Function must have external linkage. + /// Usage: ipf.template set<&FreeFn>(); + /// \tparam Function The function pointer. + //************************************************************************* + template + void set() + { + static_assert(etl::is_invocable_r::value, + "etl::inplace_function: function pointer is not compatible with the inplace_function signature"); + + clear(); + vtable = vtable_type::template for_compile_time_function(); + } + + //************************************************************************* + /// Set from a compile-time bound non-const member + instance reference. + /// Instance must have external linkage when used as an NTTP. + /// \tparam TObject The object type. + /// \tparam Method The member function pointer. + /// \tparam Instance The instance reference. + //************************************************************************* + template + void set() + { + static_assert(etl::is_invocable_r::value, + "etl::inplace_function: bound member function is not compatible with the inplace_function signature"); + + clear(); + vtable = vtable_type::template for_compile_time_member(); + } + + //************************************************************************* + /// Set from a compile-time bound const member + instance reference. + /// Instance must have external linkage when used as an NTTP. + /// \tparam TObject The object type. + /// \tparam Method The member function pointer. + /// \tparam Instance The instance reference. + //************************************************************************* + template + void set() + { + static_assert(etl::is_invocable_r::value, + "etl::inplace_function: bound member function is not compatible with the inplace_function signature"); + + clear(); + vtable = vtable_type::template for_compile_time_const_member(); + } + + //************************************************************************* + /// operator() + instance reference (compile-time, non-const) + /// \tparam TObject The object type. + /// \tparam Instance The instance reference. + //************************************************************************* + template , + typename = etl::enable_if_t::value && + etl::has_call_operator::value && + !etl::function_traits::is_const>> + void set() + { + static_assert(etl::is_invocable_r::value, + "etl::inplace_function: bound lambda/functor is not compatible with the inplace_function signature"); + + clear(); + vtable = vtable_type::template for_compile_time_operator(); + } + + //************************************************************************* + /// operator() + instance reference (compile-time, const) + /// \tparam TObject The object type. + /// \tparam Instance The instance reference. + //************************************************************************* + template , + typename = etl::enable_if_t::value && + etl::has_call_operator::value && + etl::function_traits::is_const>> + void set() + { + static_assert(etl::is_invocable_r::value, + "etl::inplace_function: bound lambda/functor is not compatible with the inplace_function signature"); + + clear(); + vtable = vtable_type::template for_compile_time_operator(); + } + + //************************************************************************* + /// Free function (external linkage) + /// \tparam Function The function pointer. + /// \return The constructed inplace_function. + //************************************************************************* + template + static this_type create() + { + return this_type(vtable_type::template for_compile_time_function()); + } + + //************************************************************************* + /// Non-const member + instance reference (compile time) + /// \tparam TObject The object type. + /// \tparam Method The member function pointer. + /// \tparam Instance The instance reference. + /// \return The constructed inplace_function. + //************************************************************************* + template + static this_type create() + { + return this_type(vtable_type::template for_compile_time_member()); + } + + //************************************************************************* + /// Const member + instance reference (compile time) + /// \tparam TObject The object type. + /// \tparam Method The member function pointer. + /// \tparam Instance The instance reference. + /// \return The constructed inplace_function. + //************************************************************************* + template + static this_type create() + { + return this_type(vtable_type::template for_compile_time_const_member()); + } + + //************************************************************************* + /// operator() + instance reference (compile time, non-const) + /// \tparam TObject The object type. + /// \tparam Instance The instance reference. + /// \return The constructed inplace_function. + //************************************************************************* + template + static this_type create() + { + return this_type(vtable_type::template for_compile_time_operator()); + } + + //************************************************************************* + /// Assignment operator + /// \param rhs The other inplace_function to copy from. + /// \return The current inplace_function. + //************************************************************************* + inplace_function& operator =(const inplace_function& rhs) + { + if (this != &rhs) + { + clear(); + clone_from(rhs); + } + + return *this; + } + + //************************************************************************* + /// Move assignment operator + /// \param rhs The other inplace_function to move from. + /// \return The current inplace_function. + //************************************************************************* + inplace_function& operator =(inplace_function&& rhs) noexcept + { + if (this != &rhs) + { + clear(); + move_from(rhs); + } + + return *this; + } + + //************************************************************************* + /// Assignment from nullptr + /// Clears the inplace_function. + /// \param nullptr_t Null pointer + /// \return The current inplace_function. + //************************************************************************* + inplace_function& operator =(etl::nullptr_t) noexcept + { + clear(); + + return *this; + } + + //************************************************************************* + /// Assignment from function pointer + /// \param f The function pointer. + /// \return The current inplace_function. + //************************************************************************* + inplace_function& operator =(function_ptr f) + { + set(f); + + return *this; + } + + //************************************************************************* + /// Assignment from lambda/functor (runtime). + /// Enabled only for class types that are not etl::inplace_function. + /// \tparam TLambda The lambda/functor type. + /// \param lambda The lambda/functor. + /// \return The current inplace_function. + //************************************************************************* + template ::type, + typename = etl::enable_if_t::value && !is_inplace_function::value, void>> + inplace_function& operator =(TLambda&& lambda) + { + set(etl::forward(lambda)); + + return *this; + } + + //************************************************************************* + /// Swap with another inplace_function + /// \param other The other inplace_function. + //************************************************************************* + void swap(inplace_function& other) noexcept + { + if (this == &other) + { + return; + } + + do_swap(vtable, storage_ptr(), + other.vtable, other.storage_ptr()); + } + + //************************************************************************* + /// Checks if the inplace_function is valid (has a target). + /// \return true if the inplace_function is valid, false if not. + //************************************************************************* + ETL_NODISCARD + bool is_valid() const noexcept + { + return (vtable != nullptr); + } + + //************************************************************************* + /// Boolean conversion operator. + /// Checks if the inplace_function is valid (has a target). + /// \return true if the inplace_function is valid, false if not. + //************************************************************************* + ETL_NODISCARD + explicit operator bool() const noexcept + { + return is_valid(); + } + + //************************************************************************* + /// Execute the inplace_function. + /// Triggers inplace_function_uninitialized assertion if not valid. + /// \param args The arguments to pass to the inplace_function. + /// \return The result of the invocation. + //************************************************************************* + TReturn operator()(TArgs... args) const + { + ETL_ASSERT(is_valid(), ETL_ERROR(inplace_function_uninitialized)); + + return vtable->invoke(storage_ptr(), etl::forward(args)...); + } + + //************************************************************************* + /// Execute the inplace_function, if valid. + /// 'void' return. + /// \param args The arguments to pass to the inplace_function. + /// \return true if executed, false if not valid. + //************************************************************************* + template + typename etl::enable_if_t::value, bool> + call_if(TArgs... args) const + { + if (is_valid()) + { + vtable->invoke(storage_ptr(), etl::forward(args)...); + return true; + } + else + { + return false; + } + } + + //************************************************************************* + /// Execute the inplace_function if valid. + /// Non 'void' return. + /// \param args The arguments to pass to the inplace_function. + /// \return etl::optional with result if executed, empty etl::optional if not valid. + //************************************************************************* + template + typename etl::enable_if_t::value, etl::optional> + call_if(TArgs... args) const + { + etl::optional result; + + if (is_valid()) + { + result = vtable->invoke(storage_ptr(), etl::forward(args)...); + } + + return result; + } + + //************************************************************************* + /// Execute the inplace_function if valid or call alternative. + /// Run time alternative. + /// \tparam TAlternative The alternative callable. + /// \param alternative The alternative callable. + /// \param args The arguments to pass to the inplace_function or alternative. + /// \return The result of the invocation. + //************************************************************************* + template + TReturn call_or(TAlternative&& alternative, TArgs... args) const + { + if (is_valid()) + { + return vtable->invoke(storage_ptr(), etl::forward(args)...); + } + else + { + return etl::forward(alternative)(etl::forward(args)...); + } + } + + //************************************************************************* + /// Execute the inplace_function if valid or call alternative. + /// Compile time alternative. + /// \tparam Alternative The alternative callable. + /// \param args The arguments to pass to the inplace_function or alternative. + /// \return The result of the invocation. + //************************************************************************* + template + TReturn call_or(TArgs... args) const + { + if (is_valid()) + { + return vtable->invoke(storage_ptr(), etl::forward(args)...); + } + else + { + return (Alternative)(etl::forward(args)...); + } + } + + //************************************************************************* + /// Clears the inplace_function + /// Destroys any stored callable object. + //************************************************************************* + void clear() noexcept + { + if (is_valid()) + { + if (vtable->destroy) + { + vtable->destroy(storage_ptr()); + } + + vtable = nullptr; + } + } + + //************************************************************************* + /// Get the storage size + /// \return The storage size. + //************************************************************************* + ETL_NODISCARD + static constexpr size_t size() noexcept + { + return Object_Size; + } + + //************************************************************************* + /// Get the storage alignment + /// \return The storage alignment. + //************************************************************************* + ETL_NODISCARD + static constexpr size_t alignment() noexcept + { + return Object_Alignment; + } + + private: + + // Allow cross-size access to internals + template + friend class inplace_function; + + //************************************************************************* + // Direct-initialization constructor for CT-bound vtables (no payload). + //************************************************************************* + explicit inplace_function(const vtable_type* vt) noexcept + : vtable(vt) + , storage() + {} + + //************************************************************************* + // clone_from + //************************************************************************* + template + void clone_from(const etl::inplace_function& other) + { + vtable = other.vtable; + + if (vtable && vtable->copy) + { + vtable->copy(&other.storage, &storage); + } + } + + //************************************************************************* + // move_from + //************************************************************************* + template + void move_from(etl::inplace_function& other) + { + vtable = other.vtable; + + if (vtable && vtable->move) + { + vtable->move(&other.storage, &storage); + } + + other.vtable = nullptr; + } + + //************************************************************************* + // Internal swap implementation + //************************************************************************* + static void do_swap(const vtable_type*& vt_a, void* storage_a, + const vtable_type*& vt_b, void* storage_b) noexcept + { + const bool a_valid = (vt_a != nullptr); + const bool b_valid = (vt_b != nullptr); + + if (!a_valid && !b_valid) + { + return; + } + + if (!a_valid) + { + // Only 'b' is valid + if (vt_b->move) + { + vt_b->move(storage_b, storage_a); + } + else if (vt_b->copy) + { + vt_b->copy(storage_b, storage_a); + } + vt_a = vt_b; + vt_b = nullptr; + return; + } + + // Only 'a' is valid + if (!b_valid) + { + if (vt_a->move) + { + vt_a->move(storage_a, storage_b); + } + else if (vt_a->copy) + { + vt_a->copy(storage_a, storage_b); + } + vt_b = vt_a; + vt_a = nullptr; + return; + } + + // Both valid. + // If both have no payload (compile-time bound: no move/copy) just swap vtable pointers. + if (!vt_a->move && !vt_a->copy && + !vt_b->move && !vt_b->copy) + { + const vtable_type* tmp = vt_a; + vt_a = vt_b; + vt_b = tmp; + return; + } + + // General case + alignas(Object_Alignment) unsigned char temp[Object_Size]; + + // a -> temp + if (vt_a->move) + { + vt_a->move(storage_a, temp); + } + else if (vt_a->copy) + { + vt_a->copy(storage_a, temp); + } + + // b -> a + if (vt_b->move) + { + vt_b->move(storage_b, storage_a); + } + else if (vt_b->copy) + { + vt_b->copy(storage_b, storage_a); + } + + // temp -> b + if (vt_a->move) + { + vt_a->move(temp, storage_b); + } + else if (vt_a->copy) + { + vt_a->copy(temp, storage_b); + } + + // Swap vtable pointers + const vtable_type* tmp = vt_a; + vt_a = vt_b; + vt_b = tmp; + } + + //************************************************************************* + // Get pointer to storage as a void* + //************************************************************************* + void* storage_ptr() const noexcept + { + return const_cast(static_cast(&storage)); + } + + const vtable_type* vtable = nullptr; + storage_type storage; + }; + + //************************************************************************* + /// Declare an inplace_function from a single type. + /// \tparam TSignature The function signature. + /// \tparam TStorage The storage type. + //************************************************************************* + template + using inplace_function_for = etl::inplace_function), + alignof(etl::decay_t)>; + + //************************************************************************* + /// Declare an inplace_function from multiple candidates. Picks the largest size/alignment. + /// \tparam TSignature The function signature. + /// \tparam T... The stored candidates types. + //************************************************************************* + template + using inplace_function_for_any = etl::inplace_function, etl::decay_t...>::size, + etl::largest, etl::decay_t...>::alignment>; + + //************************************************************************* + /// Helper to build an inplace_function from a free function pointer. + /// Usage: auto ipf = etl::make_inplace_function(); + /// \param function The function pointer. + /// \return The constructed inplace_function. + //************************************************************************* + template + ETL_NODISCARD + etl::inplace_function + make_inplace_function(TReturn(*function)(TArgs...)) + { + using function_ptr = TReturn(*)(TArgs...); + + return etl::inplace_function_for(function); + } + + //************************************************************************* + /// Helper to build an inplace_function bound to a non-const member function. + /// Usage: auto ipf = etl::make_inplace_function(obj, &Type::Method); + /// \param method The member function pointer. + /// \param obj The object. + /// \return The constructed inplace_function. + //************************************************************************* + template ::template member_target> + ETL_NODISCARD + etl::inplace_function + make_inplace_function(TReturn (TObject::*method)(TArgs...), TObject& obj) + { + return etl::inplace_function_for(method, obj); + } + + //************************************************************************* + /// Helper to build an inplace_function bound to a const member function. + /// Usage: auto ipf = etl::make_inplace_function(obj, &Type::Method) // obj is const + /// \param method The member function pointer. + /// \param obj The object. + /// \return The constructed inplace_function. + //************************************************************************* + template ::template const_member_target> + ETL_NODISCARD + etl::inplace_function + make_inplace_function(TReturn (TObject::*method)(TArgs...) const, const TObject& obj) + { + return etl::inplace_function_for(method, obj); + } + + //************************************************************************* + /// Helper to build an inplace_function from a lambda/functor (non-generic, non-overloaded). + /// Deduces R(Args...) from &T::operator(). + /// Prefer this when operator() is not overloaded/templated. + /// \param lambda The lambda/functor. + /// \return The constructed inplace_function. + //************************************************************************* + template ::type, + typename = typename etl::enable_if_t::value && !is_inplace_function::value, void>, + typename TSignature = typename etl::function_traits::function_type> + ETL_NODISCARD + etl::inplace_function + make_inplace_function(TLambda&& lambda) + { + return etl::inplace_function_for(etl::forward(lambda)); + } + + //************************************************************************* + /// Storage-deducing maker (non-class only) to avoid colliding with functor maker + /// \param function The function-like object. + /// \return The constructed inplace_function. + //************************************************************************* + template ::type, + typename = typename etl::enable_if_t::value, int>> + ETL_NODISCARD + inplace_function_for make_inplace_function(TType&& function) + { + // If T is a function type, use a function pointer for storage sizing. + using storage_t = typename etl::conditional::value, + typename etl::add_pointer::type, + T>::type; + + return inplace_function_for(etl::forward(function)); + } + +#if ETL_USING_CPP17 + //************************************************************************* + /// Make an inplace_function from a function at compile time. + /// Only participates for free function pointers (not member function pointers). + /// \tparam Function The function pointer. + /// \return The constructed inplace_function. + //************************************************************************* + template ::value && etl::is_function>::value>> + ETL_NODISCARD + auto make_inplace_function() + { + using function_type = typename etl::function_traits::function_type; + + return etl::inplace_function::template create(); + } + + //************************************************************************* + /// Make an inplace_function from a member function at compile time. + /// \tparam TObject The object type. + /// \tparam Method The member function pointer. + /// \tparam Instance The instance reference. + /// \return The constructed inplace_function. + //************************************************************************* + template ::value>, + typename = etl::enable_if_t::is_const>> + ETL_NODISCARD + auto make_inplace_function() + { + using function_type = typename etl::function_traits::function_type; + + return etl::inplace_function::template create(); + } + + //************************************************************************* + /// Make an inplace_function from a const member function at compile time. + /// \tparam TObject The object type. + /// \tparam Method The member function pointer. + /// \tparam Instance The instance reference. + /// \return The constructed inplace_function. + //************************************************************************* + template ::value>, + typename = etl::enable_if_t::is_const>> + ETL_NODISCARD + auto make_inplace_function() + { + using function_type = typename etl::function_traits::function_type; + + return etl::inplace_function::template create(); + } + + //************************************************************************* + /// Make an inplace_function from operator() at compile time. + /// \tparam TObject The object type. + /// \tparam Instance The instance reference. + /// \return The constructed inplace_function. + //************************************************************************* + template , + typename = etl::enable_if_t::value && etl::has_call_operator::value>> + ETL_NODISCARD + auto make_inplace_function() + { + using function_type = typename etl::function_traits::function_type; + + return etl::inplace_function::template create(); + } +#endif + + //************************************************************************* + /// Swap two inplace_functions. + /// \param lhs The first inplace_function. + /// \param rhs The second inplace_function. + //************************************************************************* + template + void swap(etl::inplace_function& lhs, etl::inplace_function& rhs) noexcept + { + lhs.swap(rhs); + } + + //************************************************************************* + /// Check inplace_function for equality with nullptr. + /// \param lhs The inplace_function. + /// \param rhs The nullptr. + /// \returns true if equal. + //************************************************************************* + template + ETL_NODISCARD + bool operator ==(const etl::inplace_function& lhs, etl::nullptr_t) + { + return !lhs.is_valid(); + } + + //************************************************************************* + /// Check inplace_function for equality with nullptr. + /// \param lhs The nullptr. + /// \param rhs The inplace_function. + /// \returns true if equal. + //************************************************************************* + template + ETL_NODISCARD + bool operator ==(etl::nullptr_t, const etl::inplace_function& rhs) + { + return !rhs.is_valid(); + } + + //************************************************************************* + /// Check inplace_function for inequality with nullptr. + /// \param lhs The inplace_function. + /// \param rhs The nullptr. + /// \returns true if not equal. + //************************************************************************* + template + ETL_NODISCARD + bool operator !=(const etl::inplace_function& lhs, etl::nullptr_t) + { + return lhs.is_valid(); + } + + //************************************************************************* + /// Check inplace_function for inequality with nullptr. + /// \param lhs The nullptr. + /// \param rhs The inplace_function. + /// \returns true if not equal. + //************************************************************************* + template + ETL_NODISCARD + bool operator !=(etl::nullptr_t, const etl::inplace_function& rhs) + { + return rhs.is_valid(); + } +} + +#endif +#endif \ No newline at end of file diff --git a/include/etl/invoke.h b/include/etl/invoke.h index efd0dc07..be9bbbfb 100644 --- a/include/etl/invoke.h +++ b/include/etl/invoke.h @@ -36,7 +36,6 @@ SOFTWARE. #include "function_traits.h" #include "type_traits.h" #include "utility.h" -#include "type_list.h" #if ETL_USING_CPP11 @@ -173,6 +172,37 @@ namespace etl static ETL_CONSTANT bool value = type::value; }; + //******************************************* + // Result type of a valid invocation. + template + struct invoke_result_impl + { + template + static auto test(int) -> decltype(etl::invoke(etl::declval(), etl::declval()...)); + + template + static void test(...); + + using type = decltype(test(0)); + }; + + //******************************************* + // Result type of a valid invocation. + template + struct invoke_result_impl> + { + template + static auto test(int) -> decltype(etl::invoke(etl::declval(), etl::declval()...)); + + template + static void test(...); + + using type = decltype(test(0)); + }; + + template + using invoke_result_impl_t = typename invoke_result_impl::type; + //******************************************* // Map raw function type to pointer. template @@ -195,7 +225,31 @@ namespace etl etl::void_t(), etl::declval()...))>, TArgs...> { - using type = decltype(etl::invoke(etl::declval(), etl::declval()...)); + private: + + using FC = private_invoke::effective_callable_t; + + public: + + using type = etl::conditional_t::value, + private_invoke::invoke_result_impl_t, + void>; + }; + + //**************************************************************************** + /// invoke_result> + template + struct invoke_result> + { + private: + + using FC = private_invoke::effective_callable_t; + + public: + + using type = etl::conditional_t::value, + private_invoke::invoke_result_impl_t, + void>; }; //******************************************* diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 26333f79..899ae047 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -201,6 +201,7 @@ add_executable(etl_tests test_index_of_type.cpp test_indirect_vector.cpp test_indirect_vector_external_buffer.cpp + test_inplace_function.cpp test_instance_count.cpp test_integral_limits.cpp test_intrusive_forward_list.cpp @@ -209,9 +210,10 @@ add_executable(etl_tests test_intrusive_queue.cpp test_intrusive_stack.cpp test_invert.cpp - test_io_port.cpp - test_iterator.cpp test_invoke.cpp + test_io_port.cpp + test_is_invocable.cpp + test_iterator.cpp test_jenkins.cpp test_largest.cpp test_limiter.cpp diff --git a/test/syntax_check/CMakeLists.txt b/test/syntax_check/CMakeLists.txt index 8df498b4..0e2627df 100644 --- a/test/syntax_check/CMakeLists.txt +++ b/test/syntax_check/CMakeLists.txt @@ -217,6 +217,7 @@ target_sources(tests PRIVATE imemory_block_allocator.h.t.cpp indirect_vector.h.t.cpp initializer_list.h.t.cpp + inplace_function.h.t.cpp instance_count.h.t.cpp integral_limits.h.t.cpp intrusive_forward_list.h.t.cpp @@ -225,6 +226,7 @@ target_sources(tests PRIVATE intrusive_queue.h.t.cpp intrusive_stack.h.t.cpp invert.h.t.cpp + invoke.h.t.cpp io_port.h.t.cpp ipool.h.t.cpp ireference_counted_message_pool.h.t.cpp diff --git a/test/syntax_check/inplace_function.h.t.cpp b/test/syntax_check/inplace_function.h.t.cpp new file mode 100644 index 00000000..af3ac7d4 --- /dev/null +++ b/test/syntax_check/inplace_function.h.t.cpp @@ -0,0 +1,29 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2025 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 diff --git a/test/syntax_check/invoke.h.t.cpp b/test/syntax_check/invoke.h.t.cpp new file mode 100644 index 00000000..40ed37b2 --- /dev/null +++ b/test/syntax_check/invoke.h.t.cpp @@ -0,0 +1,29 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2025 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 diff --git a/test/test_inplace_function.cpp b/test/test_inplace_function.cpp new file mode 100644 index 00000000..27fd5407 --- /dev/null +++ b/test/test_inplace_function.cpp @@ -0,0 +1,1780 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2025 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/inplace_function.h" +#include "etl/vector.h" + +#include +#include +#include +#include +#include + +// Enable exactly one of these at a time to see the corresponding static_assert fire. +//#define ETL_NEGATIVE_TEST_INPLACE_FUNCTION_BAD_RETURN +//#define ETL_NEGATIVE_TEST_INPLACE_FUNCTION_RVALUE_PARAM_MISMATCH_NONCONST +//#define ETL_NEGATIVE_TEST_INPLACE_FUNCTION_RVALUE_PARAM_MISMATCH_CONST + +namespace +{ + //***************************************************************************** + enum class FunctionCalled : int + { + Not_Called, + Free_Void_Called, + Free_Int_Called, + Free_Reference_Called, + Free_Moveableonly_Called, + Normal_Called, + Normal_Returning_Void_Called, + Alternative_Called, + Member_Void_Called, + Member_Void_Const_Called, + Member_Int_Called, + Member_Int_Const_Called, + Member_Reference_Called, + Member_Reference_Const_Called, + Member_Moveableonly_Called, + Member_Static_Called, + Operator_Called, + Operator_Const_Called, + Lambda_Called + }; + + FunctionCalled function_called = FunctionCalled::Not_Called; + + //***************************************************************************** + const int VALUE1 = 1; + const int VALUE2 = 2; + bool parameter_correct = false; + + //***************************************************************************** + // Object data structure. + //***************************************************************************** + struct Data + { + int ipf; + }; + + //***************************************************************************** + // Object moveable only data structure. + //***************************************************************************** + struct MoveableOnlyData + { + MoveableOnlyData() = default; + ~MoveableOnlyData() = default; + MoveableOnlyData(const MoveableOnlyData&) = delete; + MoveableOnlyData& operator=(const MoveableOnlyData&) = delete; + MoveableOnlyData(MoveableOnlyData&&) = default; + MoveableOnlyData& operator=(MoveableOnlyData&&) = default; + int ipf; + }; + + //***************************************************************************** + // The free function taking no parameters. + //***************************************************************************** + static void free_void() + { + function_called = FunctionCalled::Free_Void_Called; + } + + //***************************************************************************** + // The free function taking an int parameter. + //***************************************************************************** + void free_int(int i, int j) + { + function_called = FunctionCalled::Free_Int_Called; + parameter_correct = (i == VALUE1) && (j == VALUE2); + } + + //***************************************************************************** + // The free function taking a Data reference parameter. + //***************************************************************************** + void free_reference(const Data& data, int j) + { + function_called = FunctionCalled::Free_Reference_Called; + parameter_correct = (data.ipf == VALUE1) && (j == VALUE2); + } + + //***************************************************************************** + // The free function taking a moveable only parameter. + //***************************************************************************** + void free_moveableonly(MoveableOnlyData&& data) + { + function_called = FunctionCalled::Free_Moveableonly_Called; + parameter_correct = (data.ipf == VALUE1); + } + + //***************************************************************************** + // The normal function. + //***************************************************************************** + int normal(int i, int j) + { + function_called = FunctionCalled::Normal_Called; + parameter_correct = (i == VALUE1) && (j == VALUE2); + + return i + j; + } + + //***************************************************************************** + // The normal function returning void. + //***************************************************************************** + void normal_returning_void(int i, int j) + { + function_called = FunctionCalled::Normal_Returning_Void_Called; + parameter_correct = (i == VALUE1) && (j == VALUE2); + } + + //***************************************************************************** + // The alternative function. + //***************************************************************************** + int alternative(int i, int j) + { + function_called = FunctionCalled::Alternative_Called; + parameter_correct = (i == VALUE1) && (j == VALUE2); + + return i + j + 1; + } + + //***************************************************************************** + // The throwing function. + //***************************************************************************** + void throwing_void() + { + throw std::runtime_error("throwing function"); + } + + int throwing_normal(int, int) + { + throw std::runtime_error("throwing function with two parameters"); + } + + //******************************************* + // Functor with non-const operator() + struct Functor + { + void operator()() + { + function_called = FunctionCalled::Operator_Called; + } + }; + + //******************************************* + // Functor with const operator() + struct FunctorConst + { + void operator()() const + { + function_called = FunctionCalled::Operator_Const_Called; + } + }; + + //******************************************* + int times_2(int a) + { + return a * 2; + } + + //******************************************* + // Free int return. + int free_int_return(int i, int j) + { + return i + j; + } + +#if ETL_USING_CPP17 + static auto global_lambda = [](int i, int j) + { + function_called = FunctionCalled::Lambda_Called; + parameter_correct = (i == VALUE1) && (j == VALUE2); + }; +#endif + + //******************************************* + // Functor with that is destructible and movable + //******************************************* + struct DestructibleMovableObject + { + DestructibleMovableObject() + : call_count(0) + { + destructor_called = false; + copy_constructor_called = false; + move_constructor_called = false; + function_operator_called = false; + } + + ~DestructibleMovableObject() + { + destructor_called = true; + } + + DestructibleMovableObject(const DestructibleMovableObject& other) + : call_count(other.call_count) + { + copy_constructor_called = true; + } + + DestructibleMovableObject(DestructibleMovableObject&& other) + : call_count(other.call_count) + { + move_constructor_called = true; + } + + void operator()(int, int) + { + function_operator_called = true; + ++call_count; + } + + static bool destructor_called; + static bool copy_constructor_called; + static bool move_constructor_called; + static bool function_operator_called; + + int call_count; + }; + + bool DestructibleMovableObject::destructor_called = false; + bool DestructibleMovableObject::copy_constructor_called = false; + bool DestructibleMovableObject::move_constructor_called = false; + bool DestructibleMovableObject::function_operator_called = false; + + //******************************************* + // Sized functor + template + struct FunctorSized + { + FunctorSized() + : data() + { + } + + int operator()() + { + function_called = FunctionCalled::Operator_Called; + + return Size; + } + + int size() const + { + return Size; + } + + char data[Size]; + }; +} + +//***************************************************************************** +// The test class with member functions. +//***************************************************************************** +class Object +{ +public: + + //******************************************* + constexpr Object() + : call_count(0) + { + } + + //******************************************* + // void + void member_void() + { + function_called = FunctionCalled::Member_Void_Called; + } + + void member_void_const() const + { + function_called = FunctionCalled::Member_Void_Const_Called; + } + + //******************************************* + // int + int member_int(int i, int j) + { + function_called = FunctionCalled::Member_Int_Called; + parameter_correct = (i == VALUE1) && (j == VALUE2); + + ++call_count; + + return i + j + 1; + } + + int member_int_const(int i, int j) const + { + function_called = FunctionCalled::Member_Int_Const_Called; + parameter_correct = (i == VALUE1) && (j == VALUE2); + + ++call_count; + + return i + j + 1; + } + + //******************************************* + // reference + void member_reference(const Data& data, int j) + { + function_called = FunctionCalled::Member_Reference_Called; + parameter_correct = (data.ipf == VALUE1) && (j == VALUE2); + } + + void member_reference_const(const Data& data, int j) const + { + function_called = FunctionCalled::Member_Reference_Const_Called; + parameter_correct = (data.ipf == VALUE1) && (j == VALUE2); + + ++call_count; + } + + //******************************************* + // moveable only data + void member_moveableonly(MoveableOnlyData&& data) + { + function_called = FunctionCalled::Member_Moveableonly_Called; + parameter_correct = (data.ipf == VALUE1); + + ++call_count; + } + + //******************************************* + // static + static void member_static(const Data& data, int j) + { + function_called = FunctionCalled::Member_Static_Called; + parameter_correct = (data.ipf == VALUE1) && (j == VALUE2); + } + + //******************************************* + // operator() + void operator()() + { + function_called = FunctionCalled::Operator_Called; + + ++call_count; + } + + //******************************************* + // operator() const + void operator()() const + { + function_called = FunctionCalled::Operator_Const_Called; + + ++call_count; + } + + mutable int call_count; +}; + +Object object_static; +constexpr const Object const_object_static; + +Functor functor_static; +const FunctorConst const_functor_static; + +namespace +{ + //***************************************************************************** + // Initialises the test results. + //***************************************************************************** + struct SetupFixture + { + SetupFixture() + { + function_called = FunctionCalled::Not_Called; + parameter_correct = false; + } + }; + + SUITE(test_inplace_function) + { + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_inplace_function_types) + { + using InplaceFunction = etl::inplace_function; + + // Check the return type. + CHECK_TRUE((std::is_same::value)); + + // Check the argument types. + CHECK_TRUE((std::is_same>::value)); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_inplace_function_size_and_alignment) + { + using InplaceFunction84 = etl::inplace_function; + using InplaceFunction168 = etl::inplace_function; + + // Check the sizes. + CHECK_EQUAL(8, InplaceFunction84::size()); + CHECK_EQUAL(16, InplaceFunction168::size()); + + // Check the alignments. + CHECK_EQUAL(4, InplaceFunction84::alignment()); + CHECK_EQUAL(8, InplaceFunction168::alignment()); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_is_valid_false) + { + etl::inplace_function ipf; + + CHECK(!ipf.is_valid()); + CHECK(!ipf); + + CHECK_THROW(ipf(), etl::inplace_function_uninitialized); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_is_valid_true) + { + auto lambda = [] {}; + + etl::inplace_function ipf(lambda); + + CHECK(ipf.is_valid()); + CHECK(ipf); + CHECK_NO_THROW(ipf()); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_is_valid_after_clear) + { + auto lambda = [] {}; + + etl::inplace_function ipf(lambda); + + CHECK_TRUE(ipf.is_valid()); + ipf.clear(); + CHECK_FALSE(ipf.is_valid()); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_free_void_run_time) + { + etl::inplace_function ipf(free_void); + + ipf(); + + CHECK(function_called == FunctionCalled::Free_Void_Called); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_free_void_compile_time) + { + auto ipf = etl::inplace_function::create(); + + ipf(); + + CHECK(function_called == FunctionCalled::Free_Void_Called); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_free_void_run_time) + { + auto ipf = etl::make_inplace_function(free_void); + + ipf(); + + CHECK(function_called == FunctionCalled::Free_Void_Called); + } + +#if ETL_USING_CPP17 + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_free_void_compile_time) + { + auto ipf = etl::make_inplace_function<&free_void>(); + + ipf(); + + CHECK(function_called == FunctionCalled::Free_Void_Called); + } +#endif + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_throwing) + { + { + auto ipf = etl::inplace_function::create(); + + CHECK_THROW(ipf(), std::runtime_error); + CHECK_THROW(ipf.call_if(), std::runtime_error); + } + + { + auto ipf = etl::inplace_function::create(); + + CHECK_THROW({ipf.call_or(alternative, VALUE1, VALUE2);}, std::runtime_error); + CHECK_THROW({ipf.call_or(VALUE1, VALUE2);}, std::runtime_error); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_free_int_run_time) + { + etl::inplace_function ipf(free_int); + + ipf(VALUE1, VALUE2); + + CHECK(function_called == FunctionCalled::Free_Int_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_free_int_compile_time) + { + auto ipf = etl::inplace_function::create(); + + ipf(VALUE1, VALUE2); + + CHECK(function_called == FunctionCalled::Free_Int_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_free_int_run_time) + { + auto ipf = etl::make_inplace_function(free_int); + + ipf(VALUE1, VALUE2); + + CHECK(function_called == FunctionCalled::Free_Int_Called); + CHECK(parameter_correct); + } + +#if ETL_USING_CPP17 + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_free_int_compile_time) + { + auto ipf = etl::make_inplace_function<&free_int>(); + + ipf(VALUE1, VALUE2); + + CHECK(function_called == FunctionCalled::Free_Int_Called); + CHECK(parameter_correct); + } +#endif + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_free_reference_run_time) + { + etl::inplace_function ipf(free_reference); + + Data data; + data.ipf = VALUE1; + + ipf(data, VALUE2); + + CHECK(function_called == FunctionCalled::Free_Reference_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_free_reference_compile_time) + { + auto ipf = etl::inplace_function::create(); + + Data data; + data.ipf = VALUE1; + + ipf(data, VALUE2); + + CHECK(function_called == FunctionCalled::Free_Reference_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_free_reference_run_time) + { + auto ipf = etl::make_inplace_function(free_reference); + + Data data; + data.ipf = VALUE1; + + ipf(data, VALUE2); + + CHECK(function_called == FunctionCalled::Free_Reference_Called); + CHECK(parameter_correct); + } + +#if ETL_USING_CPP17 + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_free_reference_compile_time) + { + auto ipf = etl::make_inplace_function<&free_reference>(); + + Data data; + data.ipf = VALUE1; + + ipf(data, VALUE2); + + CHECK(function_called == FunctionCalled::Free_Reference_Called); + CHECK(parameter_correct); + } +#endif + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_free_moveableonly_run_time) + { + etl::inplace_function ipf(free_moveableonly); + + MoveableOnlyData data; + data.ipf = VALUE1; + + ipf(std::move(data)); + + CHECK(function_called == FunctionCalled::Free_Moveableonly_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_free_moveableonly_compile_time) + { + auto ipf = etl::inplace_function::create(); + + MoveableOnlyData data; + data.ipf = VALUE1; + + ipf(std::move(data)); + + CHECK(function_called == FunctionCalled::Free_Moveableonly_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_free_moveableonly_run_time) + { + auto ipf = etl::make_inplace_function(free_moveableonly); + + MoveableOnlyData data; + data.ipf = VALUE1; + + ipf(std::move(data)); + + CHECK(function_called == FunctionCalled::Free_Moveableonly_Called); + CHECK(parameter_correct); + } + +#if ETL_USING_CPP17 + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_free_moveableonly_compile_time) + { + auto ipf = etl::make_inplace_function<&free_moveableonly>(); + + MoveableOnlyData data; + data.ipf = VALUE1; + + ipf(std::move(data)); + + CHECK(function_called == FunctionCalled::Free_Moveableonly_Called); + CHECK(parameter_correct); + } +#endif + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_lambda_int) + { + auto lambda = [](int i, int j) { function_called = FunctionCalled::Lambda_Called; parameter_correct = (i == VALUE1) && (j == VALUE2); }; + + etl::inplace_function ipf(lambda); + + ipf(VALUE1, VALUE2); + + CHECK(function_called == FunctionCalled::Lambda_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_inplace_function_lambda_int_run_time) + { + auto lambda = [](int i, int j) { function_called = FunctionCalled::Lambda_Called; parameter_correct = (i == VALUE1) && (j == VALUE2); }; + + auto ipf = etl::make_inplace_function(lambda); + + ipf(VALUE1, VALUE2); + + CHECK(function_called == FunctionCalled::Lambda_Called); + CHECK(parameter_correct); + } + +#if ETL_USING_CPP17 + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_inplace_function_lambda_int_compile_time) + { + static auto lambda = [](int i, int j) { function_called = FunctionCalled::Lambda_Called; parameter_correct = (i == VALUE1) && (j == VALUE2); }; + + auto ipf = etl::make_inplace_function(); + + ipf(VALUE1, VALUE2); + + CHECK(function_called == FunctionCalled::Lambda_Called); + CHECK(parameter_correct); + } +#endif + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_capturing_lambda_int) + { + int value = VALUE1; + + auto lambda = [value](int i, int j) -> int { function_called = FunctionCalled::Lambda_Called; parameter_correct = (i == VALUE1) && (j == VALUE2); return value + VALUE1 + VALUE2; }; + + etl::inplace_function ipf(lambda); + + int result = ipf(VALUE1, VALUE2); + + CHECK(function_called == FunctionCalled::Lambda_Called); + CHECK(parameter_correct); + CHECK_EQUAL(result, VALUE1 + VALUE1 + VALUE2); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_inplace_function_capturing_lambda_int_run_time) + { + int value = VALUE1; + + auto lambda = [value](int i, int j) -> int { function_called = FunctionCalled::Lambda_Called; parameter_correct = (i == VALUE1) && (j == VALUE2); return value + VALUE1 + VALUE2; }; + + auto ipf = etl::make_inplace_function(lambda); + + int result = ipf(VALUE1, VALUE2); + + CHECK(function_called == FunctionCalled::Lambda_Called); + CHECK(parameter_correct); + CHECK_EQUAL(result, VALUE1 + VALUE1 + VALUE2); + } + +#if ETL_USING_CPP17 + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_inplace_function_capturing_lambda_int_compile_time) + { + int value = VALUE1; + + static auto lambda = [value](int i, int j) -> int { function_called = FunctionCalled::Lambda_Called; parameter_correct = (i == VALUE1) && (j == VALUE2); return value + VALUE1 + VALUE2; }; + + auto ipf = etl::make_inplace_function(); + + int result = ipf(VALUE1, VALUE2); + + CHECK(function_called == FunctionCalled::Lambda_Called); + CHECK(parameter_correct); + CHECK_EQUAL(result, VALUE1 + VALUE1 + VALUE2); + } +#endif + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_member_operator_void) + { + Object object; + + etl::inplace_function ipf(object); + + ipf(); + + CHECK_EQUAL(0, object.call_count); // Ensure the original object is not modified. + CHECK(function_called == FunctionCalled::Operator_Called); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_member_operator_void_const) + { + const Object object; + + etl::inplace_function ipf(object); + + ipf(); + + CHECK_EQUAL(0, object.call_count); // Ensure the original object is not modified. + CHECK(function_called == FunctionCalled::Operator_Const_Called); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_inplace_function_member_operator_void_const) + { + const FunctorConst object; + + auto ipf = etl::make_inplace_function(object); + + ipf(); + + CHECK(function_called == FunctionCalled::Operator_Const_Called); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_member_operator_void_compile_time) + { + auto ipf = etl::inplace_function::create(); + + ipf(); + + CHECK(function_called == FunctionCalled::Operator_Called); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_member_operator_void_compile_time_const) + { + auto ipf = etl::inplace_function::create(); + + ipf(); + + CHECK(function_called == FunctionCalled::Operator_Const_Called); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_inplace_functor_operator_void_run_time) + { + auto ipf = etl::make_inplace_function(functor_static); + + ipf(); + + CHECK(function_called == FunctionCalled::Operator_Called); + } + + //************************************************************************* +#if ETL_USING_CPP17 + TEST_FIXTURE(SetupFixture, test_make_inplace_functor_operator_void_compile_time) + { + auto ipf = etl::make_inplace_function(); + + ipf(); + + CHECK(function_called == FunctionCalled::Operator_Called); + } +#endif + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_inplace_function_member_operator_const_void_run_time) + { + auto ipf = etl::make_inplace_function(const_functor_static); + + ipf(); + + CHECK(function_called == FunctionCalled::Operator_Const_Called); + } + + //************************************************************************* +#if ETL_USING_CPP17 + TEST_FIXTURE(SetupFixture, test_make_inplace_function_member_operator_const_void_compile_time) + { + auto ipf = etl::make_inplace_function(); + + ipf(); + + CHECK(function_called == FunctionCalled::Operator_Const_Called); + } +#endif + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_assignment_member_operator_void) + { + Object object; + + etl::inplace_function ipf; + + ipf = object; + + ipf(); + + CHECK_EQUAL(0, object.call_count); // Ensure the original object is not modified. + CHECK(function_called == FunctionCalled::Operator_Called); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_member_void) + { + auto ipf = etl::inplace_function::create(); + + ipf(); + + CHECK(function_called == FunctionCalled::Member_Void_Called); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_inplace_function_member_void_run_time) + { + static Object object; + + auto ipf = etl::make_inplace_function(&Object::member_void, object); + + ipf(); + + CHECK_EQUAL(0, object.call_count); // Ensure the original object is not modified. + CHECK(function_called == FunctionCalled::Member_Void_Called); + } + +#if ETL_USING_CPP17 + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_inplace_function_member_void_compile_time) + { + auto ipf = etl::make_inplace_function(); + + ipf(); + + CHECK(function_called == FunctionCalled::Member_Void_Called); + } +#endif + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_member_void_const) + { + auto ipf = etl::inplace_function::create(); + + ipf(); + + CHECK(function_called == FunctionCalled::Member_Void_Const_Called); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_inplace_function_member_void_const_run_time) + { + static const Object object; + + auto ipf = etl::make_inplace_function(&Object::member_void_const, object); + + ipf(); + + CHECK_EQUAL(0, object.call_count); // Ensure the original object is not modified. + CHECK(function_called == FunctionCalled::Member_Void_Const_Called); + } + + //************************************************************************* +#if ETL_USING_CPP17 + TEST_FIXTURE(SetupFixture, test_make_inplace_function_member_void_const_compile_time) + { + auto ipf = etl::make_inplace_function(); + + ipf(); + + CHECK(function_called == FunctionCalled::Member_Void_Const_Called); + } +#endif + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_member_int) + { + auto ipf = etl::inplace_function::create(); + + ipf(VALUE1, VALUE2); + + CHECK(function_called == FunctionCalled::Member_Int_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_inplace_function_member_int_run_time) + { + Object object; + + auto ipf = etl::make_inplace_function(&Object::member_int, object); // Pass it by value to the inplace function. + + ipf(VALUE1, VALUE2); + + CHECK_EQUAL(0, object.call_count); // Ensure the original object is not modified. + CHECK(function_called == FunctionCalled::Member_Int_Called); + CHECK(parameter_correct); + } + + //************************************************************************* +#if ETL_USING_CPP17 + TEST_FIXTURE(SetupFixture, test_make_inplace_function_member_int_compile_time) + { + auto ipf = etl::make_inplace_function(); + + ipf(VALUE1, VALUE2); + + CHECK(function_called == FunctionCalled::Member_Int_Called); + CHECK(parameter_correct); + } +#endif + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_member_int_const) + { + auto ipf = etl::inplace_function::create(); + + ipf(VALUE1, VALUE2); + + CHECK(function_called == FunctionCalled::Member_Int_Const_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_inplace_function_member_int_const_run_time) + { + Object object; + + auto ipf = etl::make_inplace_function(&Object::member_int_const, object); + + ipf(VALUE1, VALUE2); + + CHECK_EQUAL(0, object.call_count); // Ensure the original object is not modified. + CHECK(function_called == FunctionCalled::Member_Int_Const_Called); + CHECK(parameter_correct); + } + + //************************************************************************* +#if ETL_USING_CPP17 + TEST_FIXTURE(SetupFixture, test_make_inplace_function_member_int_const_compile_time) + { + auto ipf = etl::make_inplace_function(); + + ipf(VALUE1, VALUE2); + + CHECK(function_called == FunctionCalled::Member_Int_Const_Called); + CHECK(parameter_correct); + } +#endif + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_member_reference) + { + Object object; + auto ipf = etl::inplace_function(&Object::member_reference, object); + + Data data; + data.ipf = VALUE1; + + ipf(data, VALUE2); + + CHECK_EQUAL(0, object.call_count); // Ensure the original object is not modified. + CHECK(function_called == FunctionCalled::Member_Reference_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_member_reference_run_time) + { + Object object; + auto ipf = etl::make_inplace_function(&Object::member_reference, object); + + Data data; + data.ipf = VALUE1; + + ipf(data, VALUE2); + + CHECK_EQUAL(0, object.call_count); // Ensure the original object is not modified. + CHECK(function_called == FunctionCalled::Member_Reference_Called); + CHECK(parameter_correct); + } + +#if ETL_USING_CPP17 + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_member_reference_compile_time) + { + auto ipf = etl::make_inplace_function(); + + Data data; + data.ipf = VALUE1; + + ipf(data, VALUE2); + + CHECK(function_called == FunctionCalled::Member_Reference_Called); + CHECK(parameter_correct); + } +#endif + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_member_reference_const) + { + Object object; + auto ipf = etl::inplace_function(&Object::member_reference_const, object); + + Data data; + data.ipf = VALUE1; + + ipf(data, VALUE2); + + CHECK_EQUAL(0, object.call_count); // Ensure the original object is not modified. + CHECK(function_called == FunctionCalled::Member_Reference_Const_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_member_reference_run_time_const) + { + Object object; + auto ipf = etl::make_inplace_function(&Object::member_reference_const, object); + + Data data; + data.ipf = VALUE1; + + ipf(data, VALUE2); + + CHECK_EQUAL(0, object.call_count); // Ensure the original object is not modified. + CHECK(function_called == FunctionCalled::Member_Reference_Const_Called); + CHECK(parameter_correct); + } + +#if ETL_USING_CPP17 + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_member_reference_compile_time_const) + { + auto ipf = etl::make_inplace_function(); + + Data data; + data.ipf = VALUE1; + + ipf(data, VALUE2); + + CHECK(function_called == FunctionCalled::Member_Reference_Const_Called); + CHECK(parameter_correct); + } +#endif + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_member_moveableonly) + { + Object object; + auto ipf = etl::inplace_function(&Object::member_moveableonly, object); + + MoveableOnlyData data; + data.ipf = VALUE1; + + ipf(std::move(data)); + + CHECK_EQUAL(0, object.call_count); // Ensure the original object is not modified. + CHECK(function_called == FunctionCalled::Member_Moveableonly_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_member_static) + { + auto ipf = etl::inplace_function(Object::member_static); + + Data data; + data.ipf = VALUE1; + + ipf(data, VALUE2); + + CHECK(function_called == FunctionCalled::Member_Static_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_member_static_run_time) + { + auto ipf = etl::make_inplace_function(Object::member_static); + + Data data; + data.ipf = VALUE1; + + ipf(data, VALUE2); + + CHECK(function_called == FunctionCalled::Member_Static_Called); + CHECK(parameter_correct); + } + +#if ETL_USING_CPP17 + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_make_member_static_compile_time) + { + auto ipf = etl::make_inplace_function(); + + Data data; + data.ipf = VALUE1; + + ipf(data, VALUE2); + + CHECK(function_called == FunctionCalled::Member_Static_Called); + CHECK(parameter_correct); + } +#endif + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_set_free_int_run_time) + { + // Start with a destructible object to check that it is properly destroyed + DestructibleMovableObject object; + etl::inplace_function ipf(object); + + ipf.set(free_int); + + CHECK_TRUE(DestructibleMovableObject::destructor_called); + + ipf(VALUE1, VALUE2); + + CHECK_EQUAL(0, object.call_count); // Ensure the original object is not modified. + CHECK(function_called == FunctionCalled::Free_Int_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_set_free_int_compile_time) + { + etl::inplace_function ipf; + + ipf.set<&free_int>(); + + ipf(VALUE1, VALUE2); + + CHECK(function_called == FunctionCalled::Free_Int_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_set_lambda_int_run_time) + { + etl::inplace_function ipf; + + auto lambda = [](int i, int j) { function_called = FunctionCalled::Lambda_Called; parameter_correct = (i == VALUE1) && (j == VALUE2); }; + + ipf.set(lambda); + + ipf(VALUE1, VALUE2); + + CHECK(function_called == FunctionCalled::Lambda_Called); + CHECK(parameter_correct); + } + +#if ETL_USING_CPP17 + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_set_lambda_int_compile_time) + { + etl::inplace_function ipf; + + ipf.set(); + + ipf(VALUE1, VALUE2); + + CHECK(function_called == FunctionCalled::Lambda_Called); + CHECK(parameter_correct); + } +#endif + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_set_functor_run_time) + { + etl::inplace_function ipf; + + Functor functor; + + ipf.set(functor); + + ipf(); + + CHECK(function_called == FunctionCalled::Operator_Called); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_set_functor_compile_time) + { + etl::inplace_function ipf; + + ipf.set(); + + ipf(); + + CHECK(function_called == FunctionCalled::Operator_Called); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_set_member_reference_run_time) + { + Object object; + etl::inplace_function ipf; + + ipf.set(&Object::member_reference, object); + + Data data; + data.ipf = VALUE1; + + ipf(data, VALUE2); + + CHECK_EQUAL(0, object.call_count); // Ensure the original object is not modified. + CHECK(function_called == FunctionCalled::Member_Reference_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_set_member_reference_compile_time) + { + Object object; + etl::inplace_function ipf; + + ipf.set(&Object::member_reference, object); + + Data data; + data.ipf = VALUE1; + + ipf(data, VALUE2); + + CHECK(function_called == FunctionCalled::Member_Reference_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_set_member_reference_const_run_time) + { + const Object object; + etl::inplace_function ipf; + + ipf.set(&Object::member_reference_const, object); + + Data data; + data.ipf = VALUE1; + + ipf(data, VALUE2); + + CHECK_EQUAL(0, object.call_count); // Ensure the original object is not modified. + CHECK(function_called == FunctionCalled::Member_Reference_Const_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_set_member_reference_const_compile_time) + { + const Object object; + etl::inplace_function ipf; + + ipf.set(&Object::member_reference_const, object); + + Data data; + data.ipf = VALUE1; + + ipf(data, VALUE2); + + CHECK(function_called == FunctionCalled::Member_Reference_Const_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_copy_construct_run_time_contruction) + { + Object object; + + auto d1 = etl::inplace_function(&Object::member_int, object); + auto d2(d1); + + d2(VALUE1, VALUE2); + + CHECK_EQUAL(0, object.call_count); // Ensure the original object is not modified. + CHECK(function_called == FunctionCalled::Member_Int_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_copy_construct_compile_time_contruction) + { + auto d1 = etl::inplace_function::create(); + auto d2(d1); + + d2(VALUE1, VALUE2); + + CHECK(function_called == FunctionCalled::Member_Int_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_copy_construct_different_size) + { + auto d1 = etl::inplace_function(free_int); + auto d2 = etl::inplace_function(free_int); + auto d3 = etl::inplace_function(free_int); + auto d4 = etl::inplace_function(free_int); + + // These should not trigger any static asserts. + decltype(d1) d1_1_copy(d1); + decltype(d1) d1_4_copy(d4); + + decltype(d2) d2_1_copy(d1); + decltype(d2) d2_2_copy(d2); + + decltype(d3) d3_3_copy(d3); + decltype(d3) d3_4_copy(d4); + + CHECK_EQUAL(d1.size(), d1_1_copy.size()); + CHECK_EQUAL(d1.alignment(), d1_1_copy.alignment()); + + CHECK_EQUAL(d1.size(), d1_4_copy.size()); + CHECK_EQUAL(d1.alignment(), d1_4_copy.alignment()); + + CHECK_EQUAL(d2.size(), d2_1_copy.size()); + CHECK_EQUAL(d2.alignment(), d2_1_copy.alignment()); + + CHECK_EQUAL(d2.size(), d2_2_copy.size()); + CHECK_EQUAL(d2.alignment(), d2_2_copy.alignment()); + + CHECK_EQUAL(d3.size(), d3_3_copy.size()); + CHECK_EQUAL(d3.alignment(), d3_3_copy.alignment()); + + CHECK_EQUAL(d3.size(), d3_4_copy.size()); + CHECK_EQUAL(d3.alignment(), d3_4_copy.alignment()); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_copy_assignment) + { + DestructibleMovableObject object; + + auto d1 = etl::inplace_function(object); + etl::inplace_function d2; + + d2 = d1; + + d2(VALUE1, VALUE2); + + CHECK_EQUAL(0, object.call_count); // Ensure the original object is not modified. + CHECK_TRUE(DestructibleMovableObject::function_operator_called); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_move_assignment) + { + DestructibleMovableObject object; + + auto d1 = etl::inplace_function(object); + etl::inplace_function d2; + + d2 = etl::move(d1); + + d2(VALUE1, VALUE2); + + CHECK_EQUAL(0, object.call_count); // Ensure the original object is not modified. + CHECK_TRUE(DestructibleMovableObject::function_operator_called); + } + + //************************************************************************* + TEST(test_vector_of_inplace_functions) + { + etl::vector, 5> vector_of_inplace_functions; + + vector_of_inplace_functions.push_back(etl::inplace_function::create()); + + CHECK_EQUAL(42, vector_of_inplace_functions.front()(21)); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_call_or_run_time_normal) + { + auto ipf = etl::inplace_function(normal); + + int result = ipf.call_or(alternative, VALUE1, VALUE2); + + CHECK_EQUAL(VALUE1 + VALUE2, result); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_call_or_run_time_alternative) + { + etl::inplace_function ipf; + + int result = ipf.call_or(alternative, VALUE1, VALUE2); + + CHECK_EQUAL(VALUE1 + VALUE2 + 1, result); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_call_or_compile_time_alternative) + { + etl::inplace_function ipf; + + int result = ipf.call_or(VALUE1, VALUE2); + + CHECK_EQUAL(VALUE1 + VALUE2 + 1, result); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_call_or_inplace_function_alternative) + { + etl::inplace_function ipf; + + auto alt = etl::inplace_function::create(); + + int result = ipf.call_or(alt, VALUE1, VALUE2); + + CHECK_EQUAL(VALUE1 + VALUE2 + 1, result); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_call_if_and_valid) + { + auto ipf = etl::inplace_function::create(); + + etl::optional result = ipf.call_if(VALUE1, VALUE2); + + CHECK(bool(result)); + CHECK_EQUAL(VALUE1 + VALUE2, result.value()); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_call_if_and_not_valid) + { + etl::inplace_function ipf; + + etl::optional result = ipf.call_if(VALUE1, VALUE2); + + CHECK(!bool(result)); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_call_if_and_valid_returning_void) + { + auto ipf = etl::inplace_function::create(); + + bool was_called = ipf.call_if(VALUE1, VALUE2); + + CHECK(was_called); + CHECK(function_called == FunctionCalled::Normal_Returning_Void_Called); + CHECK(parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_call_if_and_not_valid_returning_void) + { + etl::inplace_function ipf; + + bool was_called = ipf.call_if(VALUE1, VALUE2); + + CHECK(!was_called); + CHECK(function_called == FunctionCalled::Not_Called); + CHECK(!parameter_correct); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_inplace_function_for) + { + FunctorSized<32> functor32; + + auto ipf = etl::inplace_function_for(functor32); + + int size = 0; + + function_called = FunctionCalled::Not_Called; + size = ipf(); + CHECK_EQUAL(functor32.size(), size); + CHECK_TRUE(function_called == FunctionCalled::Operator_Called); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_inplace_function_for_any) + { + FunctorSized<8> functor8; + FunctorSized<16> functor16; + FunctorSized<32> functor32; + + auto ipf = etl::inplace_function_for_any, FunctorSized<16>, FunctorSized<32>>(); + int size = 0; + + function_called = FunctionCalled::Not_Called; + ipf.set(functor8); + size = ipf(); + CHECK_TRUE(function_called == FunctionCalled::Operator_Called); + CHECK_EQUAL(functor8.size(), size); + + function_called = FunctionCalled::Not_Called; + ipf.set(functor16); + size = ipf(); + CHECK_TRUE(function_called == FunctionCalled::Operator_Called); + CHECK_EQUAL(functor16.size(), size); + + function_called = FunctionCalled::Not_Called; + ipf.set(functor32); + size = ipf(); + CHECK_TRUE(function_called == FunctionCalled::Operator_Called); + CHECK_EQUAL(functor32.size(), size); + } + + //************************************************************************* + TEST(make_inplace_function_from_free_function_name) + { + // Create from function name (decays to function pointer) + auto ipf = etl::make_inplace_function(&free_int_return); + + CHECK_TRUE(bool(ipf)); + CHECK_EQUAL(7, ipf(3, 4)); + } + + //************************************************************************* + TEST(make_inplace_function_from_free_function_pointer_variable) + { + // Create from explicit function pointer variable + int (*fp)(int, int) = &free_int_return; + + auto ipf = etl::make_inplace_function(fp); + + CHECK_TRUE(ipf.is_valid()); + CHECK_EQUAL(13, ipf(6, 7)); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_assignment_to_an_ipf_of_function_over_a_lambda_and_back) + { + etl::inplace_function ipf; + + int value = 1; + auto lambda = [value](int i, int j) -> int { return value + i + j; }; + + int result = 0; + + ipf = lambda; + result = ipf(2, 3); + CHECK_EQUAL(1 + 2 + 3, result); + + ipf = normal; + result = ipf(3, 4); + CHECK_EQUAL(3 + 4, result); + + ipf = lambda; + result = ipf(5, 6); + CHECK_EQUAL(1 + 5 + 6, result); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_swap_valid_valid) + { + Object object; + + // ipf1 -> normal, ipf2 -> member_int + etl::inplace_function ipf1(normal); + etl::inplace_function ipf2(&Object::member_int, object); + + int r1_before = ipf1(VALUE1, VALUE2); + int r2_before = ipf2(VALUE1, VALUE2); + CHECK_EQUAL(VALUE1 + VALUE2, r1_before); + CHECK_EQUAL(VALUE1 + VALUE2 + 1, r2_before); + + swap(ipf1, ipf2); // ADL swap uses member swap + + int r1_after = ipf1(VALUE1, VALUE2); + int r2_after = ipf2(VALUE1, VALUE2); + // After swap ipf1 should now hold 'member_int', ipf2 should hold 'normal' + CHECK_EQUAL(VALUE1 + VALUE2 + 1, r1_after); + CHECK_EQUAL(VALUE1 + VALUE2, r2_after); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_swap_valid_empty) + { + etl::inplace_function ipf_valid(normal); + etl::inplace_function ipf_empty; // default constructed + + CHECK_TRUE(ipf_valid.is_valid()); + CHECK_FALSE(ipf_empty.is_valid()); + + swap(ipf_valid, ipf_empty); + + // ipf_valid should now be empty, ipf_empty should now have 'normal' + CHECK_FALSE(ipf_valid.is_valid()); + CHECK_TRUE(ipf_empty.is_valid()); + + CHECK_EQUAL(VALUE1 + VALUE2, ipf_empty(VALUE1, VALUE2)); + CHECK_THROW(ipf_valid(VALUE1, VALUE2), etl::inplace_function_uninitialized); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_swap_empty_empty) + { + etl::inplace_function ipf_empty1; // default constructed + etl::inplace_function ipf_empty2; // default constructed + + CHECK_FALSE(ipf_empty1.is_valid()); + CHECK_FALSE(ipf_empty2.is_valid()); + + swap(ipf_empty1, ipf_empty2); + + // Both should still be empty + CHECK_FALSE(ipf_empty1.is_valid()); + CHECK_FALSE(ipf_empty2.is_valid()); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_swap_self_noop) + { + etl::inplace_function ipf(normal); + int before = ipf(VALUE1, VALUE2); + ipf.swap(ipf); // self-swap should be a no-op + int after = ipf(VALUE1, VALUE2); + CHECK_EQUAL(before, after); + CHECK_EQUAL(VALUE1 + VALUE2, after); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_swap_runtime_functor_and_function) + { + struct PlusOne + { + int operator()(int a, int b) { return a + b + 1; } + } functor; + + etl::inplace_function ipf_functor(functor); + etl::inplace_function ipf_function(normal); + + CHECK_EQUAL(VALUE1 + VALUE2 + 1, ipf_functor(VALUE1, VALUE2)); + CHECK_EQUAL(VALUE1 + VALUE2, ipf_function(VALUE1, VALUE2)); + + ipf_functor.swap(ipf_function); + + CHECK_EQUAL(VALUE1 + VALUE2, ipf_functor(VALUE1, VALUE2)); // now holds 'normal' + CHECK_EQUAL(VALUE1 + VALUE2 + 1, ipf_function(VALUE1, VALUE2)); // now holds functor + } + +#if defined(ETL_NEGATIVE_TEST_INPLACE_FUNCTION_BAD_RETURN) + //************************************************************************* + // Triggers: return type not convertible (void -> int) + TEST(test_inplace_function_static_assert_bad_return) + { + auto bad = [](int) { /* returns void */ }; + // static_assert in lambda_stub/const_lambda_stub should trigger: + // "etl::inplace_function: bound lambda/functor is not compatible with the inplace_function signature" + auto ipf = etl::inplace_function(bad); + (void)ipf; + } +#endif + +#if defined(ETL_NEGATIVE_TEST_INPLACE_FUNCTION_RVALUE_PARAM_MISMATCH_NONCONST) + //************************************************************************* + // Triggers: parameter ref-qualification mismatch (expects rvalue, lambda takes lvalue ref) + TEST(test_inplace_function_static_assert_param_mismatch_nonconst) + { + auto bad = [](int&) { /* needs lvalue */ }; + // Not invocable with int&&, so is_compatible_callable is false, so static_assert triggers + auto ipf = etl::inplace_function(bad); + (void)ipf; + } +#endif + +#if defined(ETL_NEGATIVE_TEST_INPLACE_FUNCTION_RVALUE_PARAM_MISMATCH_CONST) + //************************************************************************* + // Same as above, but binds a const lambda to hit const_lambda_stub + TEST(test_inplace_function_static_assert_param_mismatch_const) + { + const auto bad = [](int&) { /* needs lvalue */ }; + // Not invocable with int&&, so is_compatible_callable is false, so static_assert triggers + auto ipf = etl::inplace_function(bad); + (void)ipf; + } +#endif + } +} + diff --git a/test/test_invoke.cpp b/test/test_invoke.cpp index 4e4bf10b..7d060e52 100644 --- a/test/test_invoke.cpp +++ b/test/test_invoke.cpp @@ -28,155 +28,457 @@ SOFTWARE. #include "unit_test_framework.h" +#include +#include + +#include "etl/type_traits.h" #include "etl/invoke.h" -#include namespace { - struct TestClass + //************************************************************************* + // Callable subjects + int free_add(int a, int b) { return a + b; } + int free_noexcept(int v) noexcept { return v; } + int free_throw(int v) { throw v; return v; } + + // C-style variadic function + int varfn(int first, ...) { return first; } + + //********************************************* + struct Base { - int member_obj = 42; - std::string member_str = "hello"; + int data; + Base(int d = 10) : data(d) {} - int get_int() - { - return member_obj; - } + int add(int v) { return data + v; } + int add_const(int v) const { return data + v + 1; } - int const_get_int() const - { - return member_obj + 10; - } - void set_int(int v) - { - member_obj = v; - } - static int static_func(int x) - { - return x * 2; - } + int ref_only(int v)& { return data + v + 2; } + int rref_only(int v)&& { return data + v + 3; } + + static int static_function(int v) { return v + 1; } + int noexcept_member(int v) noexcept { return data + v; } }; - int standalone_func(int a, int b) + //********************************************* + struct Derived : Base { - return a + b; - } + Derived(int d = 20) : Base(d) {} + }; + //********************************************* + struct VolatileBase + { + int x; + VolatileBase(int v = 0) : x(v) {} + int read() const volatile { return x; } + }; + + //********************************************* struct Functor { - int operator()(int x) const - { - return x * 5; - } + int x; + explicit Functor(int x_) : x(x_) {} + int operator()(int i) { return i + x; } }; - SUITE(test_invoke) + //********************************************* + struct ConstFunctor { - //************************************************************************* - TEST(test_type_traits_functions) + int x; + explicit ConstFunctor(int x_) : x(x_) {} + int operator()(int i) const { return i + x; } + }; + + //********************************************* + struct OverloadedFunctor + { + int operator()(int i)& { return i + 1; } + int operator()(int i)&& { return i + 2; } + }; + + //********************************************* + struct MoveOnlyFunctor + { + MoveOnlyFunctor() = default; + MoveOnlyFunctor(const MoveOnlyFunctor&) = delete; + MoveOnlyFunctor(MoveOnlyFunctor&&) = default; + int operator()(int i) { return i + 1; } + }; + + //********************************************* + struct NoThrowFunctor + { + int operator()(int i) const noexcept { return i + 2; } + }; + + //********************************************* + struct ThrowingFunctor + { + int operator()(int) { throw 42; } + }; + + //********************************************* + struct MemberObj + { + MemberObj(int v) : i(v) { - CHECK_TRUE(etl::is_function::value); - CHECK_TRUE(!etl::is_function::value); - CHECK_TRUE(!etl::is_function::value); - CHECK_TRUE(!etl::is_function::value); - CHECK_TRUE(!etl::is_function::value); } - //************************************************************************* - TEST(test_type_traits_member_pointers) - { - using MemObjPtr = int TestClass::*; - using MemFnPtr = int (TestClass::*)(); + int i; + }; - CHECK_TRUE(etl::is_member_pointer::value); - CHECK_TRUE(etl::is_member_pointer::value); - CHECK_TRUE(!etl::is_member_pointer::value); + //********************************************* + template + struct IntegralOnly + { + template ::value>::type> + int operator()(U u) { return static_cast(u) + 1; } - CHECK_TRUE(etl::is_member_object_pointer::value); - CHECK_TRUE(!etl::is_member_object_pointer::value); + template ::value>::type, typename = void> + int operator()(U) = delete; + }; - CHECK_TRUE(!etl::is_member_function_pointer::value); - CHECK_TRUE(etl::is_member_function_pointer::value); - } + //********************************************* + // Non-capturing lambda (convertible to function pointer) + static auto lambda_nc = [](int a, int b) { return a + b; }; - //************************************************************************* - TEST(test_invoke_callable) - { - CHECK_EQUAL(30, etl::invoke(standalone_func, 10, 20)); +#if ETL_USING_CPP14 + //********************************************* + // Generic lambda (C++14+) + static auto lambda_generic = [](auto x, auto y) { return x + y; }; +#endif - auto lambda = [](int x) - { return x * 3; }; - CHECK_EQUAL(15, etl::invoke(lambda, 5)); + //********************************************* + // Overload set (not directly a single callable type for traits) + int overload(int i) { return i + 10; } + long overload(long l) { return l + 20; } - Functor f; - CHECK_EQUAL(60, etl::invoke(f, 12)); + int takes_ptr(const int* p) { return *(p + 1); } - CHECK_EQUAL(8, etl::invoke(TestClass::static_func, 4)); - } + //********************************************* + struct Selective + { + template ::value, int> = 0> + int operator()(T) { return 1; } - //************************************************************************* - TEST(test_invoke_mem_func_ptr) - { - TestClass obj; - TestClass* ptr = &obj; - const TestClass const_obj; + template ::value, int> = 0> + char operator()(T) { return 2; } - CHECK_EQUAL(42, etl::invoke(&TestClass::get_int, obj)); - CHECK_EQUAL(42, etl::invoke(&TestClass::get_int, ptr)); - CHECK_EQUAL(52, etl::invoke(&TestClass::const_get_int, const_obj)); + template ::value, int> = 0> + int operator()(T) = delete; + }; +} - etl::invoke(&TestClass::set_int, obj, 99); - CHECK_EQUAL(99, obj.member_obj); +//************************************************************************* +// Unit tests for etl::is_invocable / etl::is_invocable_r +//************************************************************************* +SUITE(test_invoke) +{ + //************************************************************************* + TEST(test_invoke_result) + { + int capture_value = 5; + auto lambda_cap = [capture_value](int a) { return a + capture_value; }; - etl::invoke(&TestClass::set_int, ptr, 101); - CHECK_EQUAL(101, ptr->member_obj); - } + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, int>::value)); - //************************************************************************* - TEST(test_invoke_mem_obj_ptr) - { - TestClass obj; - TestClass* ptr = &obj; + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, int>::value)); - CHECK_EQUAL(42, etl::invoke(&TestClass::member_obj, obj)); - CHECK_EQUAL(42, etl::invoke(&TestClass::member_obj, ptr)); - CHECK_EQUAL("hello", etl::invoke(&TestClass::member_str, obj)); + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, int>::value)); - etl::invoke(&TestClass::member_obj, obj) = 1000; - CHECK_EQUAL(1000, obj.member_obj); + CHECK_TRUE((etl::is_same, int>::value)); - etl::invoke(&TestClass::member_obj, ptr) = 2000; - CHECK_EQUAL(2000, ptr->member_obj); - } + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, int>::value)); - //************************************************************************* - TEST(test_invoke_result_t) - { - using MemFnPtr = decltype(&TestClass::get_int); - using ConstMemFnPtr = decltype(&TestClass::const_get_int); - using MemObjPtr_int = decltype(&TestClass::member_obj); - using FnPtr = int (*)(int, int); - - auto val = etl::is_same, int>::value; - CHECK_TRUE(val); + CHECK_TRUE((etl::is_same, int&>::value)); - val = etl::is_same, int>::value; - CHECK_TRUE(val); - - val = etl::is_same, int>::value; - CHECK_TRUE(val); - - val = etl::is_same, int>::value; - CHECK_TRUE(val); - - val = etl::is_same, int&>::value; - CHECK_TRUE(val); - - val = etl::is_same, const int&>::value; - CHECK_TRUE(val); - - val = etl::is_same, int&>::value; - CHECK_TRUE(val); - } + CHECK_TRUE((etl::is_same, int>, int>::value)); + + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, int>::value)); +#if ETL_USING_CPP14 + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, long>::value)); +#endif + + int (*ov_int)(int) = &overload; + CHECK_TRUE((etl::is_same, int>::value)); + + long (*ov_long)(long) = &overload; + CHECK_TRUE((etl::is_same, long>::value)); + + CHECK_TRUE((etl::is_same, int>::value)); + CHECK_TRUE((etl::is_same, char>::value)); + + CHECK_TRUE((etl::is_same, int>::value)); } -} \ No newline at end of file + + //************************************************************************* + TEST(test_free_function) + { + CHECK_EQUAL(3, etl::invoke(free_add, 1, 2)); + } + + //************************************************************************* + TEST(test_free_function_pointer_variable) + { + int (*fp)(int, int) = &free_add; + + CHECK_EQUAL(3, etl::invoke(fp, 1, 2)); + } + + //************************************************************************* + TEST(test_function_pointer_const_qualification) + { + int (*const cfp)(int, int) = &free_add; + + CHECK_EQUAL(3, etl::invoke(cfp, 1, 2)); + } + + //************************************************************************* + TEST(test_free_function_noexcept) + { + CHECK_EQUAL(1, etl::invoke(free_noexcept, 1)); + } + + //************************************************************************* + TEST(test_free_function_throw) + { + CHECK_THROW(etl::invoke(free_throw, 1), int); + } + + //************************************************************************* + TEST(test_static_member_function) + { + CHECK_EQUAL(2, etl::invoke(&Base::static_function, 1)); + } + + //************************************************************************* + TEST(test_member_function) + { + Base base(10); + const Base const_base(20); + + Base* p_base = &base; + const Base* p_const_base = &const_base; + + CHECK_EQUAL(11, etl::invoke(&Base::add, base, 1)); + CHECK_EQUAL(12, etl::invoke(&Base::add, p_base, 2)); + CHECK_EQUAL(14, etl::invoke(&Base::add_const, base, 3)); + CHECK_EQUAL(15, etl::invoke(&Base::add_const, p_base, 4)); + CHECK_EQUAL(26, etl::invoke(&Base::add_const, const_base, 5)); + CHECK_EQUAL(27, etl::invoke(&Base::add_const, p_const_base, 6)); + } + + //************************************************************************* + TEST(test_member_function_noexcept) + { + Base base(10); + + CHECK_EQUAL(11, etl::invoke(&Base::noexcept_member, base, 1)); + } + + //************************************************************************* + TEST(test_member_functions_ref_qualification) + { + Base base(10); + Base& base_ref = base; + + Base&& base_rref = Base(10); + + CHECK_EQUAL(13, etl::invoke(&Base::ref_only, base_ref, 1)); + CHECK_EQUAL(15, etl::invoke(&Base::rref_only, std::move(base_rref), 2)); + } + + //************************************************************************* + TEST(test_inheritance_member_function) + { + Derived derived(10); + Derived* p_derived = &derived; + + CHECK_EQUAL(11, etl::invoke(&Base::add, derived, 1)); + CHECK_EQUAL(12, etl::invoke(&Base::add, p_derived, 2)); + } + + //************************************************************************* + TEST(test_member_function_volatile) + { + VolatileBase volatile_base(10); + + CHECK_EQUAL(10, etl::invoke(&VolatileBase::read, volatile_base)); + } + + //************************************************************************* + TEST(test_member_object_pointer) + { + MemberObj obj(10); + + CHECK_EQUAL(10, etl::invoke(&MemberObj::i, obj)); + CHECK_EQUAL(20, etl::invoke(&MemberObj::i, obj) = 20); + } + + //************************************************************************* + TEST(test_functor_non_const) + { + Functor functor(10); + + CHECK_EQUAL(12, etl::invoke(functor, 2)); + } + + //************************************************************************* + TEST(test_functor_const) + { + const ConstFunctor functor(10); + + CHECK_EQUAL(12, etl::invoke(functor, 2)); + } + + //************************************************************************* + TEST(test_move_only_functor) + { + MoveOnlyFunctor functor; + + CHECK_EQUAL(11, etl::invoke(etl::move(functor), 10)); + } + + //************************************************************************* + TEST(test_overloaded_functor_ref_qualifier) + { + OverloadedFunctor functor; + OverloadedFunctor& functor_ref = functor; + OverloadedFunctor&& functor_rref = OverloadedFunctor(); + + CHECK_EQUAL(11, etl::invoke(functor_ref, 10)); + CHECK_EQUAL(12, etl::invoke(etl::move(functor_rref), 10)); + } + + //************************************************************************* + TEST(test_nothrow_and_throwing_functors) + { + NoThrowFunctor nothrow_functor; + ThrowingFunctor throwing_functor; + + CHECK_EQUAL(12, etl::invoke(nothrow_functor, 10)); + CHECK_THROW(etl::invoke(throwing_functor, 10), int); + } + + //************************************************************************* + TEST(test_lambda_non_capturing) + { + CHECK_EQUAL(3, etl::invoke(lambda_nc, 1, 2)); + } + + //************************************************************************* + TEST(test_lambda_capturing) + { + int capture_value = 5; + auto lambda_cap = [capture_value](int a) { return a + capture_value; }; + + CHECK_EQUAL(6, etl::invoke(lambda_cap, 1)); + } + +#if ETL_USING_CPP14 + //************************************************************************* + TEST(test_lambda_generic) + { + CHECK_EQUAL(3, etl::invoke(lambda_generic, 1, 2)); + CHECK_EQUAL(3L, etl::invoke(lambda_generic, 1L, 2L)); + CHECK_CLOSE(3.5, etl::invoke(lambda_generic, 1.5, 2.0), 0.001); + } +#endif + + //************************************************************************* + TEST(test_template_functor_integral_only) + { + CHECK_EQUAL(2, etl::invoke(IntegralOnly(), 1)); + } + + //************************************************************************* + TEST(test_additional_user_defined_conversion) + { + struct Converter { operator int() const { return 11; } }; + + CHECK_EQUAL(22, etl::invoke(free_add, Converter(), 11)); // Converter -> int + } + + //************************************************************************* + TEST(test_overload_set) + { + // Selecting explicit overload + int (*ov_i)(int) = &overload; + long (*ov_l)(long) = &overload; + + CHECK_EQUAL(11, etl::invoke(ov_i, 1)); + CHECK_EQUAL(21L, etl::invoke(ov_l, 1L)); + } + + //************************************************************************* + TEST(test_deleted_overload) + { + struct DeletedOverload + { + int operator()(double d) { return static_cast(2 * d); } + int operator()(int) = delete; + }; + + CHECK_CLOSE(6, etl::invoke(DeletedOverload(), 3.14), 0.001); + } + + //************************************************************************* + TEST(test_variadic_free_function) + { + CHECK_EQUAL(1, etl::invoke(varfn, 1)); + CHECK_EQUAL(2, etl::invoke(varfn, 2, 3.14, "test")); + } + + //************************************************************************* + TEST(test_array_decay_argument) + { + int arr[] = {1, 2, 3}; + + CHECK_EQUAL(2, etl::invoke(takes_ptr, arr)); + } + + //************************************************************************* + TEST(test_template_functor_multiple_enable_if) + { + CHECK_EQUAL(1, etl::invoke(Selective(), int(0))); + CHECK_EQUAL(2, etl::invoke(Selective(), char(0))); + } + + //************************************************************************* + TEST(test_rvalue_ref_member_on_temporary) + { + struct RR + { + int f() && { return 1; } + int g() & { return 2; } + }; + + RR rr; + + CHECK_EQUAL(1, etl::invoke(&RR::f, etl::move(rr))); + CHECK_EQUAL(2, etl::invoke(&RR::g, rr)); + } +} diff --git a/test/test_is_invocable.cpp b/test/test_is_invocable.cpp new file mode 100644 index 00000000..13036c8a --- /dev/null +++ b/test/test_is_invocable.cpp @@ -0,0 +1,1011 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2025 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 +#include + +#include "etl/type_traits.h" +#include "etl/invoke.h" +#include "etl/type_list.h" + +namespace +{ + //************************************************************************* + // Callable subjects + int free_add(int a, int b) { return a + b; } + short free_short(int a) { return static_cast(a); } + int free_noexcept(int v) noexcept { return v; } + int free_throw(int v) { throw v; return v; } + + // C-style variadic function + int varfn(int first, ...) + { + return first; + } + + //********************************************* + struct Base + { + int data; + Base(int d = 10) : data(d) {} + + int add(int v) { return data + v; } + int add_const(int v) const { return data + v + 1; } + + int ref_only(int v)& { return data + v + 2; } + int rref_only(int v)&& { return data + v + 3; } + + static int static_function(int v) { return v * 2; } + int member_noexcept(int v) noexcept { return data + v; } + int member_throw(int v) { throw v; return data + v; } + }; + + //********************************************* + struct NotBase + { + int data; + NotBase(int d = 0) : data(d) {} + }; + + //********************************************* + struct Derived : Base + { + Derived(int d = 20) : Base(d) {} + }; + + //********************************************* + struct NotDerived : NotBase + { + NotDerived(int d = 20) : NotBase(d) {} + }; + + //********************************************* + struct VolatileBase + { + int x; + VolatileBase(int v = 0) : x(v) {} + int read() const volatile { return x; } + }; + + //********************************************* + struct Functor + { + int factor; + explicit Functor(int f) : factor(f) {} + int operator()(int x) { return x * factor; } + }; + + //********************************************* + struct ConstFunctor + { + int bias; + explicit ConstFunctor(int b) : bias(b) {} + int operator()(int x) const { return x + bias; } + }; + + //********************************************* + struct OverloadedFunctor + { + int operator()(int)& { return 1; } + int operator()(int)&& { return 2; } + }; + + //********************************************* + struct MoveOnlyFunctor + { + MoveOnlyFunctor() = default; + MoveOnlyFunctor(const MoveOnlyFunctor&) = delete; + MoveOnlyFunctor(MoveOnlyFunctor&&) = default; + int operator()(int x) { return x + 100; } + }; + + //********************************************* + struct NothrowFunctor + { + int operator()() const noexcept { return 7; } + }; + + //********************************************* + struct ThrowingFunctor + { + int operator()() { throw 42; } + }; + + //********************************************* + struct MemberObj + { + int i; + }; + + //********************************************* + template + struct IntegralOnly + { + template ::value>::type> + int operator()(U u) { return static_cast(u) + 10; } + + template ::value>::type, typename = void> + int operator()(U) = delete; + }; + + //********************************************* + // Non-capturing lambda (convertible to function pointer) + static auto lambda_nc = [](int a, int b) { return a + b; }; + +#if ETL_USING_CPP14 + //********************************************* + // Generic lambda (C++14+) + static auto lambda_generic = [](auto x, auto y) { return x + y; }; +#endif + + //********************************************* + // Overload set (not directly a single callable type for traits) + int overload(int) { return 0; } + long overload(long) { return 0; } + + int takes_ptr(const int* p) { return *p; } + + //********************************************* + struct Selective + { + template ::value, int> = 0> + int operator()(T) { return 1; } + + template ::value, int> = 0> + char operator()(T) { return 1; } + + template ::value, int> = 0> + int operator()(T) = delete; + }; +} + +//************************************************************************* +// Unit tests for etl::is_invocable / etl::is_invocable_r +//************************************************************************* +SUITE(test_is_invocable) +{ + //************************************************************************* + TEST(use_free_functions) + { + // Ensure that clang does not discard them as unused. + CHECK_TRUE(free_add(1, 2) != 0); + CHECK_TRUE(free_short(1) != 0); + CHECK_TRUE(free_noexcept(1) != 0); + CHECK_THROW(free_throw(1), int); + CHECK_TRUE(lambda_nc(1, 2) != 0); +#if ETL_USING_CPP14 + CHECK_TRUE(lambda_generic(1, 2) != 0); +#endif + CHECK_TRUE(varfn(1, 2, 3) != 0); + + int value = 10; + CHECK_EQUAL(10, takes_ptr(&value)); + } + + //************************************************************************* + TEST(test_free_function) + { + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable::value)); // Non-convertible argument + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable>::value)); // Non-convertible argument + } + + //************************************************************************* + TEST(test_free_function_return) + { + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_FALSE((etl::is_invocable_r::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Non-convertible argument + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_FALSE((etl::is_invocable_r>::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Non-convertible argument + } + + //************************************************************************* + TEST(test_free_function_pointer_variable) + { + int (*fp)(int, int) = &free_add; + + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable::value)); // Non-convertible argument + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable>::value)); // Non-convertible argument + } + + //************************************************************************* + TEST(test_free_function_pointer_variable_return) + { + int (*fp)(int, int) = &free_add; + + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_FALSE((etl::is_invocable_r::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Non-convertible argument + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_FALSE((etl::is_invocable_r>::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Non-convertible argument + } + + //************************************************************************* + TEST(test_function_pointer_const_qualification) + { + int (*const cfp)(int, int) = &free_add; + + CHECK_TRUE((etl::is_invocable::value)); + CHECK_TRUE((etl::is_invocable_r::value)); + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_TRUE((etl::is_invocable_r>::value)); + } + + //************************************************************************* + TEST(test_free_function_noexcept) + { + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable::value)); // Non-convertible argument + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable>::value)); // Non-convertible argument + } + + //************************************************************************* + TEST(test_free_function_noexcept_return) + { + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_FALSE((etl::is_invocable_r::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Non-convertible argument + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_FALSE((etl::is_invocable_r>::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable>::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Non-convertible argument + } + + //************************************************************************* + TEST(test_free_function_throw) + { + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable::value)); // Non-convertible argument + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable>::value)); // Non-convertible argument + } + + //************************************************************************* + TEST(test_free_function_throw_return) + { + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_FALSE((etl::is_invocable_r::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Non-convertible argument + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_FALSE((etl::is_invocable_r>::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Non-convertible argument + } + +#if ETL_USING_CPP17 + //************************************************************************* + TEST(test_no_throw_invocable) + { + CHECK_TRUE((etl::is_nothrow_invocable::value)); + CHECK_FALSE((etl::is_nothrow_invocable::value)); // Arity mismatch + CHECK_FALSE((etl::is_nothrow_invocable::value)); // Non-convertible argument + + CHECK_FALSE((etl::is_nothrow_invocable::value)); + CHECK_FALSE((etl::is_nothrow_invocable::value)); // Arity mismatch + CHECK_FALSE((etl::is_nothrow_invocable::value)); // Non-convertible argument + + CHECK_TRUE((etl::is_nothrow_invocable::value)); + CHECK_FALSE((etl::is_nothrow_invocable::value)); + + // Use etl::type_list + CHECK_TRUE((etl::is_nothrow_invocable>::value)); + CHECK_FALSE((etl::is_nothrow_invocable>::value)); // Arity mismatch + CHECK_FALSE((etl::is_nothrow_invocable>::value)); // Non-convertible argument + + CHECK_FALSE((etl::is_nothrow_invocable>::value)); + CHECK_FALSE((etl::is_nothrow_invocable>::value)); // Arity mismatch + CHECK_FALSE((etl::is_nothrow_invocable>::value)); // Non-convertible argument + + CHECK_TRUE((etl::is_nothrow_invocable>::value)); + CHECK_FALSE((etl::is_nothrow_invocable>::value)); + } + + //************************************************************************* + TEST(test_no_throw_invocable_return) + { + CHECK_TRUE((etl::is_nothrow_invocable_r::value)); + CHECK_FALSE((etl::is_nothrow_invocable_r::value)); // Return mismatch + CHECK_FALSE((etl::is_nothrow_invocable_r::value)); // Arity mismatch + CHECK_FALSE((etl::is_nothrow_invocable_r::value)); // Non-convertible argument + + CHECK_FALSE((etl::is_nothrow_invocable_r::value)); + CHECK_FALSE((etl::is_nothrow_invocable_r::value)); // Return mismatch + CHECK_FALSE((etl::is_nothrow_invocable_r::value)); // Arity mismatch + CHECK_FALSE((etl::is_nothrow_invocable_r::value)); // Non-convertible argument + + CHECK_TRUE((etl::is_nothrow_invocable_r::value)); + CHECK_FALSE((etl::is_nothrow_invocable_r::value)); + + // Use etl::type_list + CHECK_TRUE((etl::is_nothrow_invocable_r>::value)); + CHECK_FALSE((etl::is_nothrow_invocable_r>::value)); // Return mismatch + CHECK_FALSE((etl::is_nothrow_invocable_r>::value)); // Arity mismatch + CHECK_FALSE((etl::is_nothrow_invocable_r>::value)); // Non-convertible argument + + CHECK_FALSE((etl::is_nothrow_invocable_r>::value)); + CHECK_FALSE((etl::is_nothrow_invocable_r>::value)); // Return mismatch + CHECK_FALSE((etl::is_nothrow_invocable_r>::value)); // Arity mismatch + CHECK_FALSE((etl::is_nothrow_invocable_r>::value)); // Non-convertible argument + + CHECK_TRUE((etl::is_nothrow_invocable_r>::value)); + CHECK_FALSE((etl::is_nothrow_invocable_r>::value)); + } +#endif + + //************************************************************************* + TEST(test_static_member_function) + { + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable::value)); // Non-convertible argument + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable>::value)); // Non-convertible argument + } + + //************************************************************************* + TEST(test_static_member_function_return) + { + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_FALSE((etl::is_invocable_r::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Non-convertible argument + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_FALSE((etl::is_invocable_r>::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Non-convertible argument + } + + //************************************************************************* + TEST(test_member_function) + { + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable::value)); // Wrong object type + CHECK_FALSE((etl::is_invocable::value)); // Non-convertible argument + + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable::value)); // Wrong object type + CHECK_FALSE((etl::is_invocable::value)); // Non-convertible argument + + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable::value)); // Wrong object type + CHECK_FALSE((etl::is_invocable::value)); // Non-convertible argument + CHECK_FALSE((etl::is_invocable::value)); // Not const function + + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable::value)); // Wrong object type + CHECK_FALSE((etl::is_invocable::value)); // Non-convertible argument + CHECK_FALSE((etl::is_invocable::value)); // Not const function + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable>::value)); // Wrong object type + CHECK_FALSE((etl::is_invocable>::value)); // Non-convertible argument + + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable>::value)); // Wrong object type + CHECK_FALSE((etl::is_invocable>::value)); // Non-convertible argument + + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable>::value)); // Wrong object type + CHECK_FALSE((etl::is_invocable>::value)); // Non-convertible argument + CHECK_FALSE((etl::is_invocable>::value)); // Not const function + + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable>::value)); // Wrong object type + CHECK_FALSE((etl::is_invocable>::value)); // Non-convertible argument + CHECK_FALSE((etl::is_invocable>::value)); // Not const function + } + + //************************************************************************* + TEST(test_member_function_return) + { + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_FALSE((etl::is_invocable_r::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Wrong object type + CHECK_FALSE((etl::is_invocable_r::value)); // Non-convertible argument + + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_FALSE((etl::is_invocable_r::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Wrong object type + CHECK_FALSE((etl::is_invocable_r::value)); // Non-convertible argument + + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_FALSE((etl::is_invocable_r::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Wrong object type + CHECK_FALSE((etl::is_invocable_r::value)); // Non-convertible argument + CHECK_FALSE((etl::is_invocable_r::value)); // Not const function + + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_FALSE((etl::is_invocable_r::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Wrong object type + CHECK_FALSE((etl::is_invocable_r::value)); // Non-convertible argument + CHECK_FALSE((etl::is_invocable_r::value)); // Not const function + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_FALSE((etl::is_invocable_r>::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Wrong object type + CHECK_FALSE((etl::is_invocable_r>::value)); // Non-convertible argument + + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_FALSE((etl::is_invocable_r>::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Wrong object type + CHECK_FALSE((etl::is_invocable_r>::value)); // Non-convertible argument + + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_FALSE((etl::is_invocable_r>::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Wrong object type + CHECK_FALSE((etl::is_invocable_r>::value)); // Non-convertible argument + CHECK_FALSE((etl::is_invocable_r>::value)); // Not const function + + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_FALSE((etl::is_invocable_r>::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Arity mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Wrong object type + CHECK_FALSE((etl::is_invocable_r>::value)); // Non-convertible argument + CHECK_FALSE((etl::is_invocable_r>::value)); // Not const function + } + + //************************************************************************* + TEST(test_member_function_noexcept) + { + CHECK_TRUE((etl::is_invocable::value)); + CHECK_TRUE((etl::is_invocable_r::value)); + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_TRUE((etl::is_invocable_r>::value)); + } + + //************************************************************************* + TEST(test_member_functions_ref_qualification) + { + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); // rvalue object disallowed + + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); // lvalue object disallowed + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); // rvalue object disallowed + + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); // lvalue object disallowed + } + + //************************************************************************* + TEST(test_inheritance_member_function) + { + CHECK_TRUE((etl::is_invocable::value)); + CHECK_TRUE((etl::is_invocable::value)); + + CHECK_FALSE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_TRUE((etl::is_invocable>::value)); + + CHECK_FALSE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); + } + + //************************************************************************* + TEST(test_member_function_volatile) + { + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); + + CHECK_TRUE((etl::is_invocable_r::value)); + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); + CHECK_TRUE((etl::is_invocable_r>::value)); + } + + //************************************************************************* + TEST(test_member_object_pointer) + { + int MemberObj::* p_int = &MemberObj::i; + + CHECK_TRUE((etl::is_invocable::value)); + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); // Needs object + CHECK_FALSE((etl::is_invocable::value)); // Wrong object type + + // Non-const object: result is int&, should satisfy int& + CHECK_TRUE((etl::is_invocable_r::value)); + + // Const object: result is const int&, cannot bind to int& + CHECK_FALSE((etl::is_invocable_r::value)); + + // Const object can bind to const int& + CHECK_TRUE((etl::is_invocable_r::value)); + } + + //************************************************************************* + TEST(test_functor_non_const) + { + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); // Arity mismatch + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); // Arity mismatch + } + + //************************************************************************* + TEST(test_functor_non_const_return) + { + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_FALSE((etl::is_invocable_r::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Arity mismatch + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_FALSE((etl::is_invocable_r>::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Arity mismatch + } + + //************************************************************************* + TEST(test_functor_const) + { + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); // Arity mismatch + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); // Arity mismatch + } + + //************************************************************************* + TEST(test_functor_const_return) + { + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_FALSE((etl::is_invocable_r::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Arity mismatch + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_FALSE((etl::is_invocable_r>::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Arity mismatch + } + + //************************************************************************* + TEST(test_move_only_functor) + { + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); // Arity mismatch + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); // Arity mismatch + } + + //************************************************************************* + TEST(test_move_only_functor_return) + { + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_FALSE((etl::is_invocable_r::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Arity mismatch + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_FALSE((etl::is_invocable_r>::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Arity mismatch + } + + //************************************************************************* + TEST(test_overloaded_functor_ref_qualifier) + { + // lvalue should pick operator()& + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); + + // rvalue temporary should pick operator()&& + CHECK_TRUE((etl::is_invocable::value)); + + // Use etl::type_list + // lvalue should pick operator()& + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); + + // rvalue temporary should pick operator()&& + CHECK_TRUE((etl::is_invocable>::value)); + } + + //************************************************************************* + TEST(test_nothrow_and_throwing_functors) + { + CHECK_TRUE((etl::is_invocable::value)); + CHECK_TRUE((etl::is_invocable_r::value)); + + CHECK_TRUE((etl::is_invocable::value)); + CHECK_TRUE((etl::is_invocable_r::value)); + } + + //************************************************************************* + TEST(test_lambda_non_capturing) + { + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); // Arity mismatch + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); // Arity mismatch + } + + //************************************************************************* + TEST(test_lambda_non_capturing_return) + { + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_FALSE((etl::is_invocable_r::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Arity mismatch + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_FALSE((etl::is_invocable_r>::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Arity mismatch + } + + //************************************************************************* + TEST(test_lambda_capturing) + { + int capture_value = 5; + auto lambda_cap = [capture_value](int a) { return a + capture_value; }; + + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); // Arity mismatch + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable, int>::value)); // Arity mismatch + } + + //************************************************************************* + TEST(test_lambda_capturing_return) + { + int capture_value = 5; + auto lambda_cap = [capture_value](int a) { return a + capture_value; }; + + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_FALSE((etl::is_invocable_r::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Arity mismatch + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_FALSE((etl::is_invocable_r>::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Arity mismatch + } + +#if ETL_USING_CPP14 + //************************************************************************* + TEST(test_lambda_generic) + { + CHECK_TRUE((etl::is_invocable::value)); + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); // Arity mismatch + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); // Arity mismatch + } + + //************************************************************************* + TEST(test_lambda_generic_return) + { + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_TRUE((etl::is_invocable_r::value)); + + CHECK_FALSE((etl::is_invocable_r::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r::value)); // Arity mismatch + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_FALSE((etl::is_invocable_r>::value)); // Return mismatch + CHECK_FALSE((etl::is_invocable_r>::value)); // Arity mismatch + } +#endif + + //************************************************************************* + TEST(test_template_functor_integral_only) + { + CHECK_TRUE((etl::is_invocable, int>::value)); + CHECK_TRUE((etl::is_invocable_r, int>::value)); + + CHECK_FALSE((etl::is_invocable, double>::value)); + CHECK_FALSE((etl::is_invocable, float>::value)); + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable, etl::type_list>::value)); + CHECK_TRUE((etl::is_invocable_r, etl::type_list>::value)); + + CHECK_FALSE((etl::is_invocable, etl::type_list>::value)); + CHECK_FALSE((etl::is_invocable_r, etl::type_list>::value)); + } + + //************************************************************************* + TEST(test_return_type_conversion) + { + CHECK_TRUE((etl::is_invocable_r::value)); // short -> int + CHECK_TRUE((etl::is_invocable_r::value)); // exact + CHECK_TRUE((etl::is_invocable_r::value)); // short -> void + CHECK_FALSE((etl::is_invocable_r::value)); // mismatch + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable_r>::value)); // short -> int + CHECK_TRUE((etl::is_invocable_r>::value)); // exact + CHECK_TRUE((etl::is_invocable_r>::value)); // short -> void + CHECK_FALSE((etl::is_invocable_r>::value)); // mismatch + } + + //************************************************************************* + TEST(test_additional_user_defined_conversion) + { + struct Converter { operator int() const { return 11; } }; + + CHECK_TRUE((etl::is_invocable::value)); // Converter -> int + CHECK_TRUE((etl::is_invocable_r::value)); + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); // Converter -> int + CHECK_TRUE((etl::is_invocable_r>::value)); + } + + //************************************************************************* + TEST(test_overload_set) + { + // Selecting explicit overload + int (*ov_i)(int) = &overload; + long (*ov_l)(long) = &overload; + + CHECK_TRUE((etl::is_invocable::value)); + CHECK_TRUE((etl::is_invocable::value)); + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_TRUE((etl::is_invocable>::value)); + } + + //************************************************************************* + TEST(test_overload_set_return) + { + int (*ov_i)(int) = &overload; + long (*ov_l)(long) = &overload; + + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_TRUE((etl::is_invocable_r::value)); + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_TRUE((etl::is_invocable_r>::value)); + } + + //************************************************************************* + TEST(test_deleted_overload) + { + struct DeletedOverload + { + int operator()(double) { return 0; } + int operator()(int) = delete; + }; + + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); // deleted + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); // deleted + } + + //************************************************************************* + TEST(test_variadic_free_function) + { + CHECK_TRUE((etl::is_invocable::value)); + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); // Needs at least one argument + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_TRUE((etl::is_invocable>::value)); + } + + //************************************************************************* + TEST(test_array_decay_argument) + { + CHECK_TRUE((etl::is_invocable::value)); + CHECK_TRUE((etl::is_invocable::value)); // array decays + CHECK_FALSE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_TRUE((etl::is_invocable>::value)); // array decays + CHECK_FALSE((etl::is_invocable>::value)); + } + + //************************************************************************* + TEST(test_template_functor_multiple_enable_if) + { + CHECK_TRUE((etl::is_invocable::value)); + CHECK_TRUE((etl::is_invocable::value)); + CHECK_FALSE((etl::is_invocable::value)); + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable>::value)); + } + + //************************************************************************* + TEST(test_rvalue_ref_member_on_temporary) + { + struct RR + { + int f() && { return 1; } + int g() & { return 2; } + }; + + CHECK_TRUE((etl::is_invocable::value)); // rvalue-qualified on temporary + CHECK_FALSE((etl::is_invocable::value)); // cannot call on lvalue + CHECK_TRUE((etl::is_invocable::value)); // lvalue-qualified + CHECK_FALSE((etl::is_invocable::value)); // cannot call on rvalue + } + + //************************************************************************* + TEST(test_void_return_exactness) + { + struct VoidFn + { + void operator()(int) {} + }; + + CHECK_TRUE((etl::is_invocable::value)); + CHECK_TRUE((etl::is_invocable_r::value)); + CHECK_FALSE((etl::is_invocable_r::value)); // void not convertible to int + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + CHECK_TRUE((etl::is_invocable_r>::value)); + CHECK_FALSE((etl::is_invocable_r>::value)); // void not convertible to int + } + + //************************************************************************* + TEST(test_conversion_chain_failure) + { + enum class E : unsigned + { + A = 0 + }; + + struct NeedsE + { + int operator()(E) + { + return 0; + } + }; + + // int not implicitly convertible to scoped enum + CHECK_FALSE((etl::is_invocable::value)); + + // Underlying type convertible when cast explicit; still trait should be false without cast + CHECK_FALSE((etl::is_invocable_r::value)); + + // Use etl::type_list + CHECK_FALSE((etl::is_invocable>::value)); + CHECK_FALSE((etl::is_invocable_r>::value)); + } + + //************************************************************************* + TEST(test_invoke_with_null_member_pointer_type_only) + { + int (Base::*pmf)(int) = nullptr; + + // Trait should report invocable even though runtime call would be UB + CHECK_TRUE((etl::is_invocable::value)); + + // Use etl::type_list + CHECK_TRUE((etl::is_invocable>::value)); + } +} diff --git a/test/vs2022/etl.vcxproj b/test/vs2022/etl.vcxproj index a79e639b..6396b761 100644 --- a/test/vs2022/etl.vcxproj +++ b/test/vs2022/etl.vcxproj @@ -3544,6 +3544,7 @@ + @@ -10347,8 +10348,10 @@ + + diff --git a/test/vs2022/etl.vcxproj.filters b/test/vs2022/etl.vcxproj.filters index 4f1f3320..6deed683 100644 --- a/test/vs2022/etl.vcxproj.filters +++ b/test/vs2022/etl.vcxproj.filters @@ -1534,7 +1534,10 @@ UnitTest++\Header Files - UnitTest++\Header Files + ETL\Utilities + + + ETL\Utilities @@ -3761,6 +3764,12 @@ Tests\Callbacks & Delegates + + Tests\Callbacks & Delegates + + + Tests\Callbacks & Delegates +