diff options
author | bbiff <[email protected]> | 2022-08-24 16:44:16 +0300 |
---|---|---|
committer | bbiff <[email protected]> | 2022-08-24 16:44:16 +0300 |
commit | 7eeb61811ba1588f20d70abeadc7b9308dd785dd (patch) | |
tree | 71e1f4bcfd0bef1563239e2bea85d8de0e5eb275 /util/generic | |
parent | 653146b941fae8faaf0cf733e371816e7c69e7d2 (diff) |
added limit parameter
Diffstat (limited to 'util/generic')
-rw-r--r-- | util/generic/function_ref.h | 122 | ||||
-rw-r--r-- | util/generic/function_ref_ut.cpp | 150 |
2 files changed, 272 insertions, 0 deletions
diff --git a/util/generic/function_ref.h b/util/generic/function_ref.h new file mode 100644 index 00000000000..c55de8694c9 --- /dev/null +++ b/util/generic/function_ref.h @@ -0,0 +1,122 @@ +#pragma once + +#include <util/generic/function.h> +#include <util/system/yassert.h> + +#include <functional> + +template <typename Signature> +class TFunctionRef; + +template <typename Ret, typename... Args, bool IsNoexcept> +class TFunctionRef<Ret(Args...) noexcept(IsNoexcept)> { +public: + using TSignature = Ret(Args...) noexcept(IsNoexcept); + +private: + union TErasedCallable { + const void* Functor; + void (*Function)(); + }; + using TProxy = Ret (*)(TErasedCallable callable, Args...); + + // Making this a lambda inside TFunctionRef ctor caused: + // "error: cannot compile this forwarded non-trivially copyable parameter yet" + // on clang-win-i686-release. + // + // Using correct noexcept specifiers here (noexcept(IsNoexcept)) caused miscompilation on clang: + // https://github.com/llvm/llvm-project/issues/55280. + template <typename Functor> + static Ret InvokeErasedFunctor(TErasedCallable callable, Args... args) { + auto& ref = *static_cast<const std::remove_reference_t<Functor>*>(callable.Functor); + return static_cast<Ret>(std::invoke(ref, std::forward<Args>(args)...)); + } + + template <typename Function> + static Ret InvokeErasedFunction(TErasedCallable callable, Args... args) { + auto* function = reinterpret_cast<Function*>(callable.Function); + return static_cast<Ret>(std::invoke(function, std::forward<Args>(args)...)); + } + + template <class F> + static constexpr bool IsInvocableUsing = std::conditional_t< + IsNoexcept, + std::is_nothrow_invocable_r<Ret, F, Args...>, + std::is_invocable_r<Ret, F, Args...>>::value; + + // clang-format off + template <class Callable> + static constexpr bool IsSuitableFunctor = + IsInvocableUsing<Callable> + && !std::is_function_v<Callable> + && !std::is_same_v<std::remove_cvref_t<Callable>, TFunctionRef>; + + template <class Callable> + static constexpr bool IsSuitableFunction = + IsInvocableUsing<Callable> + && std::is_function_v<Callable>; + // clang-format on + +public: + // Function ref should not be default constructible. + // While the function ref can have empty state (for example, Proxy_ == nullptr), + // It does not make sense in common usage cases. + TFunctionRef() = delete; + + // Construct function ref from a functor. + template <typename Functor, typename = std::enable_if_t<IsSuitableFunctor<Functor>>> + TFunctionRef(Functor&& functor) noexcept + : Callable_{ + .Functor = std::addressof(functor), + } + , Proxy_{InvokeErasedFunctor<Functor>} + { + } + + // Construct function ref from a function pointer. + template <typename Function, typename = std::enable_if_t<IsSuitableFunction<Function>>> + TFunctionRef(Function* function) noexcept + : Callable_{ + .Function = reinterpret_cast<void (*)()>(function), + } + , Proxy_{InvokeErasedFunction<Function>} + { + } + + // Copy ctors & assignment. + // Just copy pointers. + TFunctionRef(const TFunctionRef& rhs) noexcept = default; + TFunctionRef& operator=(const TFunctionRef& rhs) noexcept = default; + + Ret operator()(Args... args) const noexcept(IsNoexcept) { + return Proxy_(Callable_, std::forward<Args>(args)...); + } + +private: + TErasedCallable Callable_; + TProxy Proxy_ = nullptr; +}; + +namespace NPrivate { + + template <typename Callable, typename Signature = typename TCallableTraits<Callable>::TSignature> + struct TIsNothrowInvocable; + + template <typename Callable, typename Ret, typename... Args> + struct TIsNothrowInvocable<Callable, Ret(Args...)> { + static constexpr bool IsNoexcept = std::is_nothrow_invocable_r_v<Ret, Callable, Args...>; + using TSignature = Ret(Args...) noexcept(IsNoexcept); + }; + + template <typename Callable> + struct TCallableTraitsWithNoexcept { + using TSignature = typename TIsNothrowInvocable<Callable>::TSignature; + }; + +} // namespace NPrivate + +template <typename Callable> +TFunctionRef(Callable&&) -> TFunctionRef<typename NPrivate::TCallableTraitsWithNoexcept<Callable>::TSignature>; + +template <typename Function> +TFunctionRef(Function*) -> TFunctionRef<Function>; diff --git a/util/generic/function_ref_ut.cpp b/util/generic/function_ref_ut.cpp new file mode 100644 index 00000000000..59f9ae35cd8 --- /dev/null +++ b/util/generic/function_ref_ut.cpp @@ -0,0 +1,150 @@ +#include "function_ref.h" + +#include <library/cpp/testing/unittest/registar.h> + +Y_UNIT_TEST_SUITE(TestFunctionRef) { + template <typename Signature> + struct TTestFunction; + + template <typename Ret, typename... Args, bool IsNoexcept> + struct TTestFunction<Ret(Args...) noexcept(IsNoexcept)> { + Ret operator()(Args...) const noexcept(IsNoexcept) { + return {}; + } + }; + + Y_UNIT_TEST(NonDefaultConstructible) { + static_assert(!std::is_default_constructible_v<TFunctionRef<void()>>); + static_assert(!std::is_default_constructible_v<TFunctionRef<void() noexcept>>); + static_assert(!std::is_default_constructible_v<TFunctionRef<int(double, void********* megaptr, TTestFunction<void(int)>)>>); + } + + int F1(bool x) { + if (x) + throw 19; + return 42; + } + + int F2(bool x) noexcept { + return 42 + x; + } + + static const TTestFunction<int(bool)> C1; + static const TTestFunction<int(bool) noexcept> C2; + + Y_UNIT_TEST(Noexcept) { + static_assert(std::is_constructible_v<TFunctionRef<int(bool)>, decltype(F1)>); + static_assert(std::is_constructible_v<TFunctionRef<int(bool)>, decltype(F2)>); + static_assert(!std::is_constructible_v<TFunctionRef<int(bool) noexcept>, decltype(F1)>); + static_assert(std::is_constructible_v<TFunctionRef<int(bool) noexcept>, decltype(F2)>); + + static_assert(std::is_constructible_v<TFunctionRef<int(bool)>, decltype(C1)>); + static_assert(std::is_constructible_v<TFunctionRef<int(bool)>, decltype(C2)>); + static_assert(!std::is_constructible_v<TFunctionRef<int(bool) noexcept>, decltype(C1)>); + static_assert(std::is_constructible_v<TFunctionRef<int(bool) noexcept>, decltype(C2)>); + } + + Y_UNIT_TEST(Deduction) { + TFunctionRef ref1(F1); + TFunctionRef ref2(F2); + TFunctionRef ref3(C1); + TFunctionRef ref4(C2); + + static_assert(!std::is_nothrow_invocable_r_v<int, decltype(ref1), bool>); + static_assert(std::is_nothrow_invocable_r_v<int, decltype(ref2), bool>); + static_assert(std::is_same_v<decltype(ref1)::TSignature, int(bool)>); + static_assert(std::is_same_v<decltype(ref2)::TSignature, int(bool) noexcept>); + } + + void WithCallback(TFunctionRef<double(double, int) noexcept>); + + void Iterate(int from, int to, TFunctionRef<void(int)> callback) { + while (from < to) { + callback(from++); + } + } + + void IterateNoexcept(int from, int to, TFunctionRef<void(int) noexcept> callback) { + while (from < to) { + callback(from++); + } + } + + Y_UNIT_TEST(AsArgument) { + int sum = 0; + Iterate(0, 10, [&](int x) { sum += x; }); + UNIT_ASSERT_EQUAL(sum, 45); + + Iterate(0, 10, [&](int x) noexcept { sum += x; }); + UNIT_ASSERT_EQUAL(sum, 90); + + IterateNoexcept(0, 10, [&](int x) noexcept { sum += x; }); + UNIT_ASSERT_EQUAL(sum, 135); + + auto summer = [&](int x) { sum += x; }; + Iterate(0, 10, summer); + Iterate(0, 10, summer); + Iterate(0, 10, summer); + UNIT_ASSERT_EQUAL(sum, 270); + + TFunctionRef ref = summer; + Iterate(0, 10, ref); + UNIT_ASSERT_EQUAL(sum, 315); + } + + int GlobalSum = 0; + void AddToGlobalSum(int x) { + GlobalSum += x; + } + + Y_UNIT_TEST(FunctionPointer) { + GlobalSum = 0; + Iterate(0, 10, AddToGlobalSum); + UNIT_ASSERT_EQUAL(GlobalSum, 45); + + TFunctionRef ref1 = AddToGlobalSum; + Iterate(0, 10, ref1); + UNIT_ASSERT_EQUAL(GlobalSum, 90); + + TFunctionRef ref2{AddToGlobalSum}; + Iterate(0, 10, ref2); + UNIT_ASSERT_EQUAL(GlobalSum, 135); + } + + Y_UNIT_TEST(Reassign) { + TFunctionRef kek = [](double) { return 42; }; + kek = [](double) { return 19; }; + kek = [](int) { return 22.8; }; + } + + const char* Greet() { + return "Hello, world!"; + } + + Y_UNIT_TEST(ImplicitCasts) { + TFunctionRef<void(int)> ref = [](int x) { return x; }; + ref = [](double x) { return x; }; + ref = [](char x) { return x; }; + + TFunctionRef<int()> ref1 = [] { return 0.5; }; + ref1 = [] { return 'a'; }; + ref1 = [] { return 124u; }; + + TFunctionRef<TStringBuf()> ref2{Greet}; + } + + Y_UNIT_TEST(StatelessLambdaLifetime) { + TFunctionRef<int(int, int)> ref{[](int a, int b) { return a + b; }}; + UNIT_ASSERT_EQUAL(ref(5, 5), 10); + } + + Y_UNIT_TEST(ForwardArguments) { + char x = 'x'; + TFunctionRef<void(std::unique_ptr<int>, char&)> ref = [](std::unique_ptr<int> ptr, char& ch) { + UNIT_ASSERT_EQUAL(*ptr, 5); + ch = 'a'; + }; + ref(std::make_unique<int>(5), x); + UNIT_ASSERT_EQUAL(x, 'a'); + } +} |