diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d42d381..254a332 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,10 +49,36 @@ jobs: working-directory: test run: cmake --build build --config Debug --target test -j100 - # Windows check - test-on-windows: + # Windows-vs2026 check + test-on-windows-vs2026: if: github.repository_owner == 'Kim-J-Smith' - runs-on: windows-latest + runs-on: windows-2025-vs2026 + strategy: + matrix: + cpp_standards: [14, 17, 20, 23] + arch: [x64] + fail-fast: false + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Config with C++${{ matrix.cpp_standards }} + working-directory: test + shell: bash + run: | + cmake -B build -S . \ + -G "Visual Studio 18 2026" \ + -A ${{ matrix.arch }} \ + -DCMAKE_CXX_STANDARD=${{ matrix.cpp_standards }} + + - name: Build and test + working-directory: test + run: cmake --build build --config Debug --target test + + # Windows-vs2022 check + test-on-windows-vs2022: + if: github.repository_owner == 'Kim-J-Smith' + runs-on: windows-2022 strategy: matrix: cpp_standards: [14, 17, 20, 23] diff --git a/benchmark/runtime/benchmark_std_op.cpp b/benchmark/runtime/benchmark_std_op.cpp new file mode 100644 index 0000000..65d7c18 --- /dev/null +++ b/benchmark/runtime/benchmark_std_op.cpp @@ -0,0 +1,75 @@ +#include "benchmark.hpp" + +#include + +static void std_op_wrapper_fn_std(picobench::state& s) { + auto less = std::less{}; + auto greater = std::greater{}; + + auto f = [](std::function f) { return f(0x111, 0x222); }; + + for (auto _ : s) { + volatile bool res1 = f(less); + volatile bool res2 = f(greater); + (void)res1; (void)res2; + } +} + +static void std_op_wrapper_fn_ebd(picobench::state& s) { + auto less = std::less{}; + auto greater = std::greater{}; + + auto f = [](ebd::fn f) { return f(0x111, 0x222); }; + + for (auto _ : s) { + volatile bool res1 = f(less); + volatile bool res2 = f(greater); + (void)res1; (void)res2; + } +} + +static void std_op_wrapper_fn_fu2(picobench::state& s) { + auto less = std::less{}; + auto greater = std::greater{}; + + auto f = [](fu2::function f) { return f(0x111, 0x222); }; + + for (auto _ : s) { + volatile bool res1 = f(less); + volatile bool res2 = f(greater); + (void)res1; (void)res2; + } +} + +static void std_op_wrapper_fn_ref_ebd(picobench::state& s) { + auto less = std::less{}; + auto greater = std::greater{}; + + auto f = [](ebd::fn_ref f) { return f(0x111, 0x222); }; + + for (auto _ : s) { + volatile bool res1 = f(less); + volatile bool res2 = f(greater); + (void)res1; (void)res2; + } +} + +static void std_op_wrapper_fn_view_fu2(picobench::state& s) { + auto less = std::less{}; + auto greater = std::greater{}; + + auto f = [](fu2::function_view f) { return f(0x111, 0x222); }; + + for (auto _ : s) { + volatile bool res1 = f(less); + volatile bool res2 = f(greater); + (void)res1; (void)res2; + } +} + +BENCHMARK_UNIT(StdOperatorWrapper.FunctionWrapperAsParams); +BENCHMARK_BASELINE(std_op_wrapper_fn_std); +BENCHMARK_NOTBASE(std_op_wrapper_fn_fu2); +BENCHMARK_NOTBASE(std_op_wrapper_fn_ebd); +BENCHMARK_NOTBASE(std_op_wrapper_fn_view_fu2); +BENCHMARK_NOTBASE(std_op_wrapper_fn_ref_ebd); diff --git a/docs/release/release_notes.md b/docs/release/release_notes.md index 310a238..ae3caf0 100644 --- a/docs/release/release_notes.md +++ b/docs/release/release_notes.md @@ -1,13 +1,19 @@ ## Fixed -- Fixed a constructor bug in `fn_ref`. +- Fixed a bug where `make_fn` failed to correctly deduce a `noexcept` functor. + +- Fixed a bug in converting from `fn_ref` to `fn` / `unique_fn` / `safe_fn`. ## Changed -- `fn`, `unique_fn`, and `safe_fn` now use `is-callable-from` as the constraint, aligning with [func.wrap.move.ctor]/1. This tightens overload resolution and avoids incorrect constructor participation. +- **Lowered MSVC requirement to 19.20+** (previously required 19.34+). + +- Improved ambiguity error diagnostics for `make_fn` when the callable is ambiguous. + +- Enhanced the performance of `fn_ref`, `fn`, `unique_fn`, and `safe_fn`. -- Added negative compilation tests for the above constraints. +- `make_fn` now supports automatic deduction of callable objects with a `static operator()`. ## Notes -- `std::constant_wrapper` support is experimental as of v2.1.3. +- `std::constant_wrapper` support is experimental as of v2.1.4. diff --git a/include/embed/embed_function.hpp b/include/embed/embed_function.hpp index 0193d99..9ad77e4 100644 --- a/include/embed/embed_function.hpp +++ b/include/embed/embed_function.hpp @@ -1105,7 +1105,7 @@ inline namespace fn_traits { // Get aligned size. Rounds up to the nearest word. template struct get_aligned_size { - static constexpr std::size_t min_aligned = sizeof(void*); + static constexpr std::size_t min_aligned = sizeof(void (*) ()); static constexpr std::size_t aligned_size = ((Size - 1) / min_aligned + 1) * min_aligned; static constexpr std::size_t value = Size == 0 ? min_aligned : aligned_size; }; @@ -1122,7 +1122,7 @@ inline namespace fn_traits { static constexpr std::size_t ref_buf = sizeof(void (*) ()); static constexpr std::size_t view_buf = ref_buf; #if defined(EMBED_FN_CONFIG_USE_BIG_DEFAULT_BUFFER) - // The CommandTable size plus the buffer size is about 8 * sizeof(void). + // The CommandTable size plus the buffer size is about 8 * sizeof(void*). // TODO: The size of this buffer zone needs further examination. static constexpr std::size_t value_c1 = 6 * sizeof(void*); static constexpr std::size_t value_c2 = sizeof(::std::function); @@ -1215,8 +1215,19 @@ inline namespace fn_traits { Functor, void_t> : public std::true_type {}; + // Check static callable functor. +#if (EMBED_CXX_VERSION >= 202302L && __cpp_static_call_operator >= 202207L) + template + struct is_static_callable_functor : bool_constant< + requires (Args&&... args) { Fn::operator()(std::forward(args)...); } + > {}; +#else + template + struct is_static_callable_functor : std::false_type {}; +#endif + // Implement the `get_unique_signature`. - template + template struct get_unique_signature_impl { static_assert(always_false::value, "T must be a function pointer or pointer to member function."); @@ -1224,11 +1235,11 @@ inline namespace fn_traits { // The Config::isThrowing of ebd::fn and ebd::unique_fn is true. // So `get_unique_signature_impl` will ignore the `noexcept` specifier. -#define EMBED_DETAIL_GET_UNIQUE_SIGNATURE_IMPL_DEFINE(C, V, REF, NOEXCEPT) \ - template \ - struct get_unique_signature_impl { \ - using type = Ret(Args...) C V REF; \ - using sig_with_noexcept = Ret(Args...) C V REF NOEXCEPT; \ +#define EMBED_DETAIL_GET_UNIQUE_SIGNATURE_IMPL_DEFINE(C, V, REF, NOEXCEPT) \ + template \ + struct get_unique_signature_impl { \ + using type = Ret(Args...) C V REF; \ + using sig_with_noexcept = Ret(Args...) C V REF NOEXCEPT; \ }; EMBED_DETAIL_FN_EXPAND(EMBED_DETAIL_GET_UNIQUE_SIGNATURE_IMPL_DEFINE) @@ -1237,7 +1248,7 @@ inline namespace fn_traits { #if ( __cpp_explicit_this_parameter >= 202110L ) || ( EMBED_CXX_VERSION >= 202302L ) - // [dcl.fct]/6 function/packaged_task deduction guides and deducing this. + // [dcl.fct]/6 Deducing this. // See . // Trait to add qualifiers (const, volatile, &, &&, noexcept) to a function @@ -1256,13 +1267,52 @@ inline namespace fn_traits { # undef EMBED_DETAIL_ADD_QUALIFIER_WITH_THIS_DEFINE - template - struct get_unique_signature_impl - : public add_qualifier_with_this {}; + // MSVC (as of 19.50.35717) cannot write `requires (...) {...}` in + // enable_if_t<> in the template argument list. To work around this, + // we use `is_explicit_this_call_v` instead. + template + constexpr bool is_explicit_this_call_v = requires (Fn f, Args&&... args) { + static_cast::type + >::template add_cvref_like>(f)(std::forward(args)...); + }; + + // noexcept(false) + template + struct get_unique_signature_impl> + > : public add_qualifier_with_this {}; + + // noexcept(true) + template + struct get_unique_signature_impl> + > : public add_qualifier_with_this {}; - template - struct get_unique_signature_impl - : public add_qualifier_with_this {}; +#endif + +#if (EMBED_CXX_VERSION >= 202302L && __cpp_static_call_operator >= 202207L) + + // [func.wrap.func.con]/16.2 Static operator(). + // See . + + // noexcept(false) + template + struct get_unique_signature_impl::value + >> { // ^^^ Cannot simply write `requires (...) {...}` due to MSVC(as of 19.50.35717) bug. + using type = Ret(Args...) const; + using sig_with_noexcept = Ret(Args...) const; + }; + + // noexcept(true) + template + struct get_unique_signature_impl::value + >> { // ^^^ Cannot simply write `requires (...) {...}` due to MSVC(as of 19.50.35717) bug. + using type = Ret(Args...) const; + using sig_with_noexcept = Ret(Args...) const noexcept; + }; #endif @@ -1276,7 +1326,7 @@ inline namespace fn_traits { template struct get_unique_signature { - using impl_t = get_unique_signature_impl; + using impl_t = get_unique_signature_impl; using type = typename impl_t::type; using sig_with_noexcept = typename impl_t::sig_with_noexcept; }; @@ -1411,14 +1461,13 @@ inline namespace fn_traits { static constexpr std::size_t reg_size = sizeof(void*); static constexpr std::size_t obj_size = sizeof(T); static constexpr bool is_trivial_obj = is_call_trivial::value; + static constexpr bool is_scalar_obj = std::is_scalar::value; #if defined(__sparc_v8__) || defined(__sparcv8) // class and union object are not allowed to pass by reg in SPARC V8 (32bit). - static constexpr bool value = false; -#elif defined(__riscv) - // There is an abundance of register resources in RISC-V (a0 ~ a7). - static constexpr bool value = obj_size <= 2 * reg_size && is_trivial_obj; + static constexpr bool value = is_scalar_obj; #else - static constexpr bool value = obj_size <= reg_size && is_trivial_obj; + static constexpr bool value = + is_scalar_obj || (obj_size <= 2 * reg_size && is_trivial_obj); #endif }; @@ -1427,8 +1476,7 @@ inline namespace fn_traits { // be passed by the register rather than the stack. #if !defined(EMBED_FN_CONFIG_DISABLE_SMART_FORWARD) template - using smart_forward_t = conditional_t< - std::is_scalar::value || is_reg_passable::value, T, T&&>; + using smart_forward_t = conditional_t::value, T, T&&>; #else template using smart_forward_t = T&&; @@ -1589,28 +1637,46 @@ inline namespace fn_traits { template struct is_std_op_wrapper> : std::true_type {}; template struct is_std_op_wrapper> : std::true_type {}; + // Check whether the functor is stateless. + template + struct is_stateless : bool_constant< + is_std_op_wrapper::value || is_static_callable_functor::value + > {}; + + // Log error for make_fn. + template + EMBED_INLINE EMBED_CXX14_CONSTEXPR void make_fn_log_error() noexcept { + static_assert(always_false::value, + "[Embedded Function]: The make_fn() cannot automatically deduce an" + " appropriate function wrapper based on your input parameters." + " This might be because the parameters you passed are ambiguous," + " such as overloaded free functions, member functions, objects" + " that overload multiple operator() functions, nullptr, or just" + " non-parameters. If so, you can use 'make_fn(...)' to" + " specify the signature of target callable object to disambiguate." + " The 'Signature' is like 'void()', 'float(int,int)'." + ); + }; } // end namespace fn_traits // In the namespace "erasure_type", we define a series of // types for objects that implement type erasure. namespace erasure_type { - template - union ErasureCoreImpl { - static_assert(Size > 0, "erasure size must greater than 0"); - + // Reference storage, which used in view mode. + union ErasureRefStorage { void* fill_ptr; const void* fill_const_ptr; void (*fill_func_ptr) (); - char fill[Size]; }; template union EMBED_DETAIL_ALIAS ErasureCore { + static_assert(Size > 0, "erasure size must greater than 0"); // An array of `unsigned char` can be used to hold other objects. // See . - unsigned char pod[sizeof(ErasureCoreImpl)]; - ErasureCoreImpl ref_storage; // alignas(ref_storage) + unsigned char pod[Size]; + ErasureRefStorage ref_storage; // alignas(ref_storage) }; // Passing the `ErasureBase*` as a parameter can avoid the @@ -1652,28 +1718,47 @@ namespace erasure_type { { return *EMBED_DETAIL_LAUNDER(static_cast(access())); } }; + // ABI for passing either pointer or value. + union ErasurePass { + ErasureBase* ptr; + ErasureRefStorage val; + }; + } // end namespace erasure_type // In the namespace "invocation", we define a series of // types for objects that implement the behaviour of invocation. namespace invocation { +// Implement invocation for static call operator. +#if (EMBED_CXX_VERSION >= 202302L && __cpp_static_call_operator >= 202207L) +# define EMBED_DETAIL_STATIC_CALL_INVOKER_IMPL(C, V, REF, NOEXCEPT) \ + struct static_call { \ + template \ + static Ret invoke(erasure_pass_t, smart_forward_t... args) NOEXCEPT { \ + return Functor::operator()(std::forward(args)...); \ + } \ + }; /* end static_call */ +#else +# define EMBED_DETAIL_STATIC_CALL_INVOKER_IMPL(C, V, REF, NOEXCEPT) \ + struct static_call { /* empty struct */ }; +#endif + // Implement invocation for constant_wrapper. #if __cpp_lib_constant_wrapper >= 202603L # define EMBED_DETAIL_CW_INVOKER_IMPL(C, V, REF, NOEXCEPT) \ struct view_cw { \ template \ - static Ret invoke(erasure_base_t*, smart_forward_t... args) NOEXCEPT { \ + static Ret invoke(erasure_pass_t, smart_forward_t... args) NOEXCEPT { \ return invoke_r(Cw::value, std::forward(args)...); \ } \ template \ - static Ret invoke(erasure_base_t* base, smart_forward_t... args) NOEXCEPT { \ - auto* erased = static_cast(base); \ + static Ret invoke(erasure_pass_t base, smart_forward_t... args) NOEXCEPT { \ if constexpr (CallPointer) { \ - C V auto* obj_ptr = erased->template access(); \ + auto* obj_ptr = static_cast(base.val.fill_ptr); \ return invoke_r(Cw::value, obj_ptr, std::forward(args)...); \ } else { \ - C V auto& obj = *erased->template access(); \ + auto& obj = *static_cast(base.val.fill_ptr); \ return invoke_r(Cw::value, obj, std::forward(args)...); \ } \ } \ @@ -1691,15 +1776,16 @@ namespace invocation { struct InvokerImpl { \ public: \ using erasure_base_t = erasure_type::ErasureBase C V; \ + using erasure_pass_t = erasure_type::ErasurePass C; \ using erasure_t = erasure_type::Erasure C V; \ static constexpr bool is_rvalue_ref = \ std::is_rvalue_reference::value; \ - using invoker_type = Ret (*) ( \ - erasure_base_t* base, smart_forward_t... args); \ + using invoker_type = \ + Ret (*) (erasure_pass_t erased, smart_forward_t... args); \ \ /* Using when M_erasure is empty. */ \ struct empty { \ - static Ret invoke(erasure_base_t*, smart_forward_t...) { \ + static Ret invoke(erasure_pass_t, smart_forward_t...) { \ throw_or_terminate(); \ /* Unreachable: throw_or_terminate() is [[noreturn]] */ \ } \ @@ -1708,12 +1794,12 @@ namespace invocation { /* Using when Config::isView == false. */ \ struct inplace { \ template \ - static Ret invoke(erasure_base_t* base, smart_forward_t... args) { \ - auto* erased = static_cast(base); \ + static Ret invoke(erasure_pass_t base, smart_forward_t... args) { \ + auto* erased = static_cast(base.ptr); \ auto& fn = erased->template access(); \ using Fn = conditional_t&&, \ - C V remove_reference_t&>; \ + remove_reference_t&&, \ + remove_reference_t&>; \ return invoke_r(static_cast(fn), std::forward(args)...); \ } \ }; \ @@ -1723,20 +1809,18 @@ namespace invocation { /* Using when Functor::is_stored_origin == true. */ \ template \ static enable_if_t::value, Ret> \ - invoke(erasure_base_t* base, smart_forward_t... args) { \ - auto* erased = static_cast(base); \ - auto& fn = erased->template access(); \ - using Fn = C V remove_reference_t&; \ + invoke(erasure_pass_t base, smart_forward_t... args) { \ + auto* fn = reinterpret_cast(base.val.fill_func_ptr); \ + using Fn = remove_reference_t&; \ return invoke_r(static_cast(fn), std::forward(args)...); \ } \ \ /* Using when Functor::is_stored_origin == false. */ \ template \ static enable_if_t::value, Ret> \ - invoke(erasure_base_t* base, smart_forward_t... args) { \ - auto* erased = static_cast(base); \ - auto& fn = *(erased->template access()); \ - using Fn = C V remove_reference_t&; \ + invoke(erasure_pass_t base, smart_forward_t... args) { \ + auto& fn = *(static_cast(base.val.fill_ptr)); \ + using Fn = remove_reference_t&; \ return invoke_r(static_cast(fn), std::forward(args)...); \ } \ }; \ @@ -1744,7 +1828,7 @@ namespace invocation { /* Used for standard operator wrapper (like std::less). */ \ struct std_op_wrapper { \ template \ - static Ret invoke(erasure_base_t*, smart_forward_t... args) { \ + static Ret invoke(erasure_pass_t, smart_forward_t... args) { \ static_assert(is_std_op_wrapper::value, \ EMBED_DETAIL_REPORT_IE("'StdOperator' should be std operator wrapper"));\ C V auto fn = StdOperator{}; \ @@ -1752,6 +1836,7 @@ namespace invocation { } \ }; \ \ + EMBED_DETAIL_STATIC_CALL_INVOKER_IMPL(C, V, REF, NOEXCEPT) \ EMBED_DETAIL_CW_INVOKER_IMPL(C, V, REF, NOEXCEPT) \ }; @@ -1939,12 +2024,13 @@ namespace command { using manager_impl_t = management::ManagerImpl; using invoker_t = typename invoker_impl_t::invoker_type; using erasure_base_t = typename invoker_impl_t::erasure_base_t; + using erasure_pass_t = typename invoker_impl_t::erasure_pass_t; using manager_t = typename manager_impl_t::manager_type; manager_t m_manager; invoker_t m_invoker; - auto invoke(erasure_base_t* erased, Args&&... args) const + auto invoke(erasure_pass_t erased, Args&&... args) const noexcept(unwrap_signature::isNoexcept) -> typename unwrap_signature::ret { return m_invoker(erased, std::forward(args)...); @@ -1972,12 +2058,13 @@ namespace command { } // Check the `m_invoker` is empty::invoke. (constexpr && noexcept) - constexpr bool is_empty() const noexcept { + EMBED_NODISCARD constexpr bool is_empty() const noexcept { return m_invoker == &invoker_impl_t::empty::invoke; } + // Initialize owning function wrapper. (Enable if Functor is NOT stateless.) template > - enable_if_t::value> + enable_if_t::value> init(erasure_base_t* target, Functor&& obj) noexcept(std::is_nothrow_constructible::value) { manager_impl_t::template create(target, std::forward(obj)); @@ -1985,10 +2072,16 @@ namespace command { m_manager = &manager_impl_t::inplace::template manage; } + // Initialize owning function wrapper. (Enable if Functor is stateless.) template > - EMBED_CXX20_CONSTEXPR enable_if_t::value> + EMBED_CXX20_CONSTEXPR enable_if_t::value> init(erasure_base_t*, Functor&&) noexcept { - m_invoker = &invoker_impl_t::std_op_wrapper::template invoke; + using invoker_impl_target_t = conditional_t< + is_static_callable_functor::value, + typename invoker_impl_t::static_call, + typename invoker_impl_t::std_op_wrapper + >; + m_invoker = &invoker_impl_target_t::template invoke; m_manager = &manager_impl_t::empty::manage; } @@ -2014,12 +2107,13 @@ namespace command { using manager_impl_t = management::ManagerImpl; using invoker_t = typename invoker_impl_t::invoker_type; using erasure_base_t = typename invoker_impl_t::erasure_base_t; + using erasure_pass_t = typename invoker_impl_t::erasure_pass_t; using erasure_t = typename invoker_impl_t::erasure_t; // No m_manager because IsView = true. invoker_t m_invoker; - auto invoke(erasure_base_t* erased, Args&&... args) const + auto invoke(erasure_pass_t erased, Args&&... args) const noexcept(unwrap_signature::isNoexcept) -> typename unwrap_signature::ret { return m_invoker(erased, std::forward(args)...); @@ -2045,7 +2139,7 @@ namespace command { // Do nothing here } - // Enable if Functor is stored origin. (Enable if the functor is function pointer(FP)) + // Initialize non-owning function wrapper. (Enable if the functor is function pointer(FP)) template > enable_if_t /* Enable if the functor is function pointer(FP) */ init(erasure_base_t* target, Functor&& obj) noexcept { @@ -2055,9 +2149,9 @@ namespace command { m_invoker = &invoker_impl_t::view::template invoke; } - // Enable if Functor is stored by pointer. (Enable if the functor is neither FP or std-op-wrapper) + // Initialize non-owning function wrapper. (Enable if the functor is neither FP nor stateless-fn) template > - EMBED_CXX20_CONSTEXPR enable_if_t::value> + EMBED_CXX20_CONSTEXPR enable_if_t::value> init(erasure_base_t* target, Functor&& obj) noexcept { static_assert(!std::is_rvalue_reference::value, "function in view mode cannot be initialized with rvalue reference."); @@ -2065,11 +2159,16 @@ namespace command { m_invoker = &invoker_impl_t::view::template invoke; } - // Enable if Functor is not stored. (Enable if the functor is std-op-wrapper) + // Initialize non-owning function wrapper. (Enable if the functor is stateless-fn) template > - EMBED_CXX20_CONSTEXPR enable_if_t::value> + EMBED_CXX20_CONSTEXPR enable_if_t::value> init(erasure_base_t*, Functor&&) noexcept { - m_invoker = &invoker_impl_t::std_op_wrapper::template invoke; + using invoker_impl_target_t = conditional_t< + is_static_callable_functor::value, + typename invoker_impl_t::static_call, + typename invoker_impl_t::std_op_wrapper + >; + m_invoker = &invoker_impl_target_t::template invoke; } #if __cpp_lib_constant_wrapper >= 202603L @@ -2118,10 +2217,13 @@ namespace crtp_mixins { EMBED_DETAIL_ALL_DEFAULT(operator_call_impl) \ \ Ret operator()(Args... args) C V REF NOEXCEPT { \ - auto* self_q = static_cast(this); \ - auto* erased = &(self_q->m_erasure); \ + using erasure_t = typename Self::erasure_t; \ using command_t = const typename Self::command_t; \ + using pass_t = typename command_t::erasure_pass_t; \ + remove_cv_t erased; \ + auto* self_q = static_cast(this); \ auto& cmd = const_cast(self_q->m_command); \ + erased.ptr = const_cast(&(self_q->m_erasure)); \ return cmd.invoke(erased, std::forward(args)...); \ } \ }; \ @@ -2136,10 +2238,13 @@ namespace crtp_mixins { \ Ret operator()(Args... args) const V NOEXCEPT { \ using erasure_t = typename Self::erasure_t; \ - auto* self_q = static_cast(this); \ - auto* erased = const_cast(&(self_q->m_erasure)); \ using command_t = const typename Self::command_t; \ + using pass_t = typename command_t::erasure_pass_t; \ + remove_cv_t erased; \ + auto* self_q = static_cast(this); \ + auto& erasure = const_cast(self_q->m_erasure); \ auto& cmd = const_cast(self_q->m_command); \ + erased.val = erasure.m_core.ref_storage; \ return cmd.invoke(erased, std::forward(args)...); \ } \ }; @@ -2336,7 +2441,7 @@ namespace crtp_mixins { } // Return `true` if the object is empty. - EMBED_CXX14_CONSTEXPR bool is_empty() const noexcept { + EMBED_NODISCARD EMBED_CXX14_CONSTEXPR bool is_empty() const noexcept { auto const& self = static_cast(*this); return self.m_command.is_empty(); } @@ -2519,7 +2624,7 @@ namespace crtp_mixins { EMBED_NODISCARD EMBED_INLINE static constexpr std::size_t get_buffer_size() noexcept { return buffer_size; } - /// @brief Return `true` if the function is capyable. + /// @brief Return `true` if the function is copyable. EMBED_NODISCARD EMBED_INLINE static constexpr bool is_copyable() noexcept { return internal_is_copyable; } @@ -2863,7 +2968,8 @@ namespace crtp_mixins { // Make a function. template - inline Fn make_function_impl(CArgs&&... args) noexcept(NoThrow) { + EMBED_CXX20_CONSTEXPR inline + Fn make_function_impl(CArgs&&... args) noexcept(NoThrow) { static_assert(is_ebd_fn::value, "Fn must be the alias of `ebd::detail::function`."); return Fn{std::forward(args)...}; @@ -3131,7 +3237,7 @@ EMBED_DETAIL_TEMPLATE_BEGIN( typename Lambda, // [Auto] The lambda or functor that overloads operator() only once. // [Auto] The basic type of the functor. typename Class = detail::remove_cvref_t, - // [Auto] The buffersize of functor. + // [Auto] The buffer size of functor. std::size_t BufferSize = sizeof(Class), // [Auto] The signature of functor. typename Signature = detail::get_unique_signature_t, @@ -3264,7 +3370,8 @@ EMBED_DETAIL_REQUIRES_END( detail::is_ebd_fn::value && detail::unwrap_signature::isSignature ) -EMBED_NODISCARD inline FnWrapper make_fn(Functor&& functor) noexcept(NoThrow) { +EMBED_NODISCARD EMBED_CXX20_CONSTEXPR inline +FnWrapper make_fn(Functor&& functor) noexcept(NoThrow) { return detail::make_function_impl< /* Fn = */ FnWrapper, /* NoThrow = */ NoThrow >(std::forward(functor)); @@ -3273,19 +3380,13 @@ EMBED_NODISCARD inline FnWrapper make_fn(Functor&& functor) noexcept(NoThrow) { // When all other make_fn() fail to match the input parameters, // this function will be called as the fall back to avoid the // awful template error flood. -template -EMBED_INLINE void make_fn(...) noexcept { - static_assert(detail::always_false::value, - "[Embedded Function]: The make_fn() cannot automatically deduce an" - " appropriate function wrapper based on your input parameters." - " This might be because the parameters you passed are ambiguous," - " such as overloaded free functions, member functions, objects" - " that overload multiple operator() functions, nullptr, or just" - " non-parameters. If so, you can use 'make_fn()' to" - " specify the signature of target callable object to disambiguate." - " The 'Signature' is like 'void()', 'float(int,int)'." - ); -} +template ::isSignature)> +EMBED_INLINE EMBED_CXX14_CONSTEXPR void make_fn(...) noexcept +{ detail::make_fn_log_error(); } +template