summaryrefslogtreecommitdiffstats
path: root/util/generic
diff options
context:
space:
mode:
authorbbiff <[email protected]>2022-08-24 16:44:16 +0300
committerbbiff <[email protected]>2022-08-24 16:44:16 +0300
commit7eeb61811ba1588f20d70abeadc7b9308dd785dd (patch)
tree71e1f4bcfd0bef1563239e2bea85d8de0e5eb275 /util/generic
parent653146b941fae8faaf0cf733e371816e7c69e7d2 (diff)
added limit parameter
Diffstat (limited to 'util/generic')
-rw-r--r--util/generic/function_ref.h122
-rw-r--r--util/generic/function_ref_ut.cpp150
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');
+ }
+}