From 1925b2d10f3d9a87432e7756d420059ac3331d0b Mon Sep 17 00:00:00 2001 From: pfeatherstone <45853521+pfeatherstone@users.noreply.github.com> Date: Sun, 14 Nov 2021 21:18:06 +0000 Subject: [PATCH] [INVOKE] added is_invocable_r, invoke_r, made everything constexpr and conditionally noexcept (#2458) * - added dlib::invoke_r() - added dlib::is_invocable_r<> - made everything constexpr and conditionally noexcept ! - added tests This has required a refactor of dlib::invoke since std::mem_fn is not constexpr in c++11 * - !std::is_function replaced with std::is_object. Let's be a bit more precise * - made dlib::apply constexpr and conditionally noexcept Co-authored-by: pf --- dlib/invoke.h | 176 ++++++++++++++++++++++++++++++++++++++++--- dlib/test/invoke.cpp | 96 +++++++++++++++++++++++ 2 files changed, 260 insertions(+), 12 deletions(-) diff --git a/dlib/invoke.h b/dlib/invoke.h index 4abca8a27..f5792c11d 100644 --- a/dlib/invoke.h +++ b/dlib/invoke.h @@ -11,32 +11,153 @@ namespace dlib { // ---------------------------------------------------------------------------------------- namespace detail { - template< typename F, typename ... Args > - auto INVOKE(F&& fn, Args&& ... args) + template< typename T > + struct is_reference_wrapper : std::false_type {}; + template< typename U > + struct is_reference_wrapper> : std::true_type {}; + + template < + typename Base, + typename T, + typename Derived, + typename... Args + > + constexpr auto INVOKE( + T Base::*pmf, //pointer to member function + Derived&& ref, + Args&&... args + ) + noexcept(noexcept((std::forward(ref).*pmf)(std::forward(args)...))) -> typename std::enable_if< - std::is_member_pointer::type>::value, - decltype(std::mem_fn(fn)(std::forward(args)...))>::type + std::is_function::value && + std::is_base_of::type>::value, + decltype((std::forward(ref).*pmf)(std::forward(args)...)) + >::type { - return std::mem_fn(fn)(std::forward(args)...) ; + return (std::forward(ref).*pmf)(std::forward(args)...); } - template< typename F, typename ... Args > - auto INVOKE(F&& fn, Args&& ... args) + template< + typename Base, + typename T, + typename RefWrap, + typename... Args + > + constexpr auto INVOKE( + T Base::*pmf, //pointer to member function + RefWrap&& ref, + Args&&... args + ) + noexcept(noexcept((ref.get().*pmf)(std::forward(args)...))) + -> typename std::enable_if< + std::is_function::value && + is_reference_wrapper::type>::value, + decltype((ref.get().*pmf)(std::forward(args)...))>::type + { + return (ref.get().*pmf)(std::forward(args)...); + } + + template< + typename Base, + typename T, + typename Ptr, + typename... Args + > + constexpr auto INVOKE( + T Base::*pmf, //pointer to member function + Ptr&& ptr, + Args&&... args + ) + noexcept(noexcept(((*std::forward(ptr)).*pmf)( std::forward( args )...))) + -> typename std::enable_if< + std::is_function::value && + !is_reference_wrapper::type>::value && + !std::is_base_of::type>::value, + decltype(((*std::forward(ptr)).*pmf)(std::forward(args)...))>::type + { + return ((*std::forward(ptr)).*pmf)( std::forward( args )...); + } + + template< + typename Base, + typename T, + typename Derived + > + constexpr auto INVOKE( + T Base::*pmd, //pointer to member data + Derived&& ref + ) + noexcept(noexcept(std::forward(ref).*pmd)) + -> typename std::enable_if< + std::is_object::value && + std::is_base_of::type>::value, + decltype(std::forward(ref).*pmd)>::type + { + return std::forward(ref).*pmd; + } + + template< + typename Base, + typename T, + typename RefWrap + > + constexpr auto INVOKE( + T Base::*pmd, //pointer to member data + RefWrap&& ref + ) + noexcept(noexcept(ref.get().*pmd)) + -> typename std::enable_if< + std::is_object::value && + is_reference_wrapper::type>::value, + decltype(ref.get().*pmd)>::type + { + return ref.get().*pmd; + } + + template< + typename Base, + typename T, + typename Ptr + > + constexpr auto INVOKE( + T Base::*pmd, //pointer to member data + Ptr&& ptr + ) + noexcept(noexcept((*std::forward(ptr)).*pmd)) + -> typename std::enable_if< + std::is_object::value && + !is_reference_wrapper::type>::value && + !std::is_base_of::type>::value, + decltype((*std::forward(ptr)).*pmd)>::type + { + return (*std::forward(ptr)).*pmd; + } + + template< + typename F, + typename... Args + > + constexpr auto INVOKE( + F && f, + Args&&... args + ) + noexcept(noexcept(std::forward( f )( std::forward( args )...))) -> typename std::enable_if< !std::is_member_pointer::type>::value, - decltype(std::forward(fn)(std::forward(args)...))>::type + decltype(std::forward(f)(std::forward(args)...))>::type { - return std::forward(fn)(std::forward(args)...); + return std::forward( f )( std::forward( args )...); } } template< typename F, typename... Args> - auto invoke(F && f, Args &&... args) + constexpr auto invoke(F && f, Args &&... args) /*! ensures - identical to std::invoke(std::forward(f), std::forward(args)...) - works with C++11 onwards !*/ + noexcept(noexcept(detail::INVOKE(std::forward( f ), std::forward( args )...))) -> decltype(detail::INVOKE(std::forward( f ), std::forward( args )...)) { return detail::INVOKE(std::forward( f ), std::forward( args )...); @@ -88,10 +209,38 @@ namespace dlib // ---------------------------------------------------------------------------------------- + template + struct is_invocable_r : std::integral_constant::value && + std::is_convertible, R>::value> {}; + /*! + ensures + - identical to std::is_invocable_r + - works with C++11 onwards + !*/ + + // ---------------------------------------------------------------------------------------- + + template< typename R, typename F, typename... Args> + constexpr typename std::enable_if::value, R>::type + invoke_r(F && f, Args &&... args) + /*! + ensures + - identical to std::invoke_r(std::forward(f), std::forward(args)...) + - works with C++11 onwards + !*/ + noexcept(noexcept(dlib::invoke(std::forward( f ), std::forward( args )...))) + { + return dlib::invoke(std::forward( f ), std::forward( args )...); + } + + // ---------------------------------------------------------------------------------------- + namespace detail { template - auto apply_impl(F&& fn, Tuple&& tpl, index_sequence) + constexpr auto apply_impl(F&& fn, Tuple&& tpl, index_sequence) + noexcept(noexcept(dlib::invoke(std::forward(fn), + std::get(std::forward(tpl))...))) -> decltype(dlib::invoke(std::forward(fn), std::get(std::forward(tpl))...)) { @@ -101,12 +250,15 @@ namespace dlib } template - auto apply(F&& fn, Tuple&& tpl) + constexpr auto apply(F&& fn, Tuple&& tpl) /*! ensures - identical to std::apply(std::forward(f), std::forward(tpl)) - works with C++11 onwards !*/ + noexcept(noexcept(detail::apply_impl(std::forward(fn), + std::forward(tpl), + make_index_sequence::type >::value>{}))) -> decltype(detail::apply_impl(std::forward(fn), std::forward(tpl), make_index_sequence::type >::value>{})) diff --git a/dlib/test/invoke.cpp b/dlib/test/invoke.cpp index 4d555c5a7..a23b6dd15 100644 --- a/dlib/test/invoke.cpp +++ b/dlib/test/invoke.cpp @@ -234,6 +234,100 @@ namespace // ---------------------------------------------------------------------------------------- + const char* func_return_c_string() + { + return "hello darkness my old friend"; + } + + struct obj_return_c_string + { + obj_return_c_string() = default; + obj_return_c_string(const obj_return_c_string& rhs) = delete; + obj_return_c_string(obj_return_c_string&& rhs) = delete; + + const char* run() + { + return "i've come to talk with you again"; + } + }; + + void test_invoke_r() + { + { + static_assert(dlib::is_invocable_r::value, "should be invocable"); + auto str = dlib::invoke_r(func_return_c_string); + static_assert(std::is_same::value, "bad return type"); + DLIB_TEST(str == "hello darkness my old friend"); + } + + { + obj_return_c_string obj; + static_assert(dlib::is_invocable_r::value, "should be invocable"); + auto str = dlib::invoke_r(&obj_return_c_string::run, obj); + static_assert(std::is_same::value, "bad return type"); + DLIB_TEST(str == "i've come to talk with you again"); + } + + { + obj_return_c_string obj; + static_assert(dlib::is_invocable_r::value, "should be invocable"); + auto str = dlib::invoke_r(&obj_return_c_string::run, &obj); + static_assert(std::is_same::value, "bad return type"); + DLIB_TEST(str == "i've come to talk with you again"); + } + + { + auto obj = std::make_shared(); + static_assert(dlib::is_invocable_r::value, "should be invocable"); + auto str = dlib::invoke_r(&obj_return_c_string::run, obj); + static_assert(std::is_same::value, "bad return type"); + DLIB_TEST(str == "i've come to talk with you again"); + } + + { + std::unique_ptr obj(new obj_return_c_string()); + static_assert(dlib::is_invocable_r::value, "should be invocable"); + auto str = dlib::invoke_r(&obj_return_c_string::run, obj); + static_assert(std::is_same::value, "bad return type"); + DLIB_TEST(str == "i've come to talk with you again"); + } + + { + auto lambda_return_c_string = [] { + return "because a vision softly creeping"; + }; + static_assert(dlib::is_invocable_r::value, "should be invocable"); + auto str = dlib::invoke_r(lambda_return_c_string); + static_assert(std::is_same::value, "bad return type"); + DLIB_TEST(str == "because a vision softly creeping"); + } + } + + // ---------------------------------------------------------------------------------------- + + constexpr int multiply_ints(int i, int j) + { + return i*j; + } + + struct constexpr_object + { + constexpr int multiply_ints(int i, int j) const + { + return i*j; + } + }; + + void test_constexpr() + { + static_assert(dlib::invoke(multiply_ints, 2, 5) == 10, "this should be constexpr"); + static_assert(dlib::invoke_r(multiply_ints, 2, 5) == 10, "this should be constexpr"); + constexpr constexpr_object constexpr_obj; + static_assert(dlib::invoke(&constexpr_object::multiply_ints, constexpr_obj, 2, 5) == 10); + static_assert(dlib::invoke_r(&constexpr_object::multiply_ints, constexpr_obj, 2, 5) == 10); + } + + // ---------------------------------------------------------------------------------------- class invoke_tester : public tester { @@ -251,6 +345,8 @@ namespace test_member_functions_and_data(); test_return_types(); test_make_from_tuple(); + test_invoke_r(); + test_constexpr(); } } a; }