function_view: Add a lightweight span equivalent for functions

Change-Id: I887f844276d618297f9861e64d269dfa4e8062fd
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/meson.build b/include/meson.build
index a31fae5..d24e818 100644
--- a/include/meson.build
+++ b/include/meson.build
@@ -5,6 +5,7 @@
   'stdplus/concepts.hpp',
   'stdplus/exception.hpp',
   'stdplus/flags.hpp',
+  'stdplus/function_view.hpp',
   'stdplus/handle/copyable.hpp',
   'stdplus/handle/managed.hpp',
   'stdplus/hash.hpp',
diff --git a/include/stdplus/function_view.hpp b/include/stdplus/function_view.hpp
new file mode 100644
index 0000000..ea9e6e7
--- /dev/null
+++ b/include/stdplus/function_view.hpp
@@ -0,0 +1,183 @@
+#pragma once
+#include <cstdint>
+#include <utility>
+
+namespace stdplus
+{
+
+template <typename F>
+struct function_view;
+
+namespace detail
+{
+
+/** @brief Turn lambdas into a type erased function */
+template <typename>
+struct FViewGuide;
+
+template <typename R, typename O, bool Nx, typename... As>
+struct FViewGuide<R (O::*)(As...) noexcept(Nx)>
+{
+    using type = R(As...) noexcept(Nx);
+};
+
+template <typename R, typename O, bool Nx, typename... As>
+struct FViewGuide<R (O::*)(As...) & noexcept(Nx)>
+{
+    using type = R(As...) noexcept(Nx);
+};
+
+template <typename R, typename O, bool Nx, typename... As>
+struct FViewGuide<R (O::*)(As...) const noexcept(Nx)>
+{
+    using type = R(As...) const noexcept(Nx);
+};
+
+template <typename R, typename O, bool Nx, typename... As>
+struct FViewGuide<R (O::*)(As...) const & noexcept(Nx)>
+{
+    using type = R(As...) const noexcept(Nx);
+};
+
+template <typename F>
+struct IsFView : std::false_type
+{};
+
+template <typename... Args>
+struct IsFView<function_view<Args...>> : std::true_type
+{};
+
+template <typename F>
+concept NonFView = !IsFView<std::decay_t<F>>::value;
+
+template <typename R, bool C, bool Nx, typename... Args>
+struct function_view_base
+{
+  protected:
+    union
+    {
+        R (*fun)(Args...);
+        R (*memfun)(void*, Args...);
+    };
+    static_assert(sizeof(fun) == sizeof(memfun));
+    void* obj;
+
+    template <bool Nx2>
+    constexpr function_view_base(R (*f)(Args...) noexcept(Nx2)) noexcept :
+        fun(f), obj(nullptr)
+    {}
+
+    template <std::invocable<Args...> F>
+        requires(!C && std::same_as<std::invoke_result_t<F, Args...>, R>)
+    inline function_view_base(F& f) noexcept :
+        memfun([](void* v, Args... args) {
+            return (*reinterpret_cast<F*>(v))(std::forward<Args>(args)...);
+        }),
+        obj(std::addressof(f))
+    {}
+
+    template <std::invocable<Args...> F>
+        requires std::same_as<std::invoke_result_t<F, Args...>, R>
+    inline function_view_base(const F& f) noexcept :
+        memfun(
+            [](void* v, Args... args) {
+        return (*reinterpret_cast<const F*>(v))(std::forward<Args>(args)...);
+        }),
+        obj(const_cast<F*>(std::addressof(f)))
+    {}
+
+    constexpr function_view_base(R (*fun)(Args...), void* obj) noexcept :
+        fun(fun), obj(obj)
+    {}
+
+  public:
+    constexpr R operator()(Args... args) const noexcept(Nx)
+    {
+        if (obj == nullptr)
+        {
+            return fun(std::forward<Args>(args)...);
+        }
+        return memfun(obj, std::forward<Args>(args)...);
+    }
+};
+
+} // namespace detail
+
+template <typename R, typename... Args, bool Nx>
+struct function_view<R(Args...) noexcept(Nx)> :
+    detail::function_view_base<R, /*const=*/false, Nx, Args...>
+{
+    using _Base = detail::function_view_base<R, /*const=*/false, Nx, Args...>;
+
+    template <bool Nx2>
+    inline function_view(
+        const function_view<R(Args...) noexcept(Nx2)>& other) noexcept :
+        _Base(other.fun, other.obj)
+    {}
+
+    template <bool Nx2>
+    inline function_view(
+        const function_view<R(Args...) const noexcept(Nx2)>& other) noexcept :
+        _Base(other.fun, other.obj)
+    {}
+
+    template <bool Nx2>
+    inline function_view&
+        operator=(const function_view<R(Args...) noexcept(Nx2)>& other) noexcept
+    {
+        this->fun = other.fun;
+        this->obj = other.obj;
+        return *this;
+    }
+
+    template <bool Nx2>
+    inline function_view& operator=(
+        const function_view<R(Args...) const noexcept(Nx2)>& other) noexcept
+    {
+        this->fun = other.fun;
+        this->obj = other.obj;
+        return *this;
+    }
+
+    template <detail::NonFView F>
+    inline function_view(F&& f) noexcept : _Base(std::forward<F>(f))
+    {}
+};
+
+template <typename R, typename... Args, bool Nx>
+struct function_view<R(Args...) const noexcept(Nx)> :
+    detail::function_view_base<R, /*const=*/true, Nx, Args...>
+{
+    using _Base = detail::function_view_base<R, /*const=*/true, Nx, Args...>;
+
+    template <bool Nx2>
+    inline function_view(
+        const function_view<R(Args...) const noexcept(Nx2)>& other) noexcept :
+        _Base(other.fun, other.obj)
+    {}
+
+    template <bool Nx2>
+    inline function_view& operator=(
+        const function_view<R(Args...) const noexcept(Nx2)>& other) noexcept
+    {
+        this->fun = other.fun;
+        this->obj = other.obj;
+        return *this;
+    }
+
+    template <detail::NonFView F>
+    inline function_view(F&& f) noexcept : _Base(std::forward<F>(f))
+    {}
+
+    friend struct function_view<R(Args...) noexcept(Nx)>;
+};
+
+template <typename R, typename... Args, bool Nx>
+function_view(R (*)(Args...) noexcept(Nx))
+    -> function_view<R(Args...) const noexcept(Nx)>;
+
+template <typename F>
+function_view(F) -> function_view<
+    typename detail::FViewGuide<decltype(&F::operator())>::type>;
+
+} // namespace stdplus
diff --git a/src/function_view.cpp b/src/function_view.cpp
new file mode 100644
index 0000000..a592b7f
--- /dev/null
+++ b/src/function_view.cpp
@@ -0,0 +1 @@
+#include <stdplus/function_view.hpp>
diff --git a/src/meson.build b/src/meson.build
index 6d17d3d..0714b56 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -45,6 +45,7 @@
   'cancel.cpp',
   'exception.cpp',
   'flags.cpp',
+  'function_view.cpp',
   'handle/copyable.cpp',
   'handle/managed.cpp',
   'hash.cpp',
diff --git a/test/function_view.cpp b/test/function_view.cpp
new file mode 100644
index 0000000..ff95f46
--- /dev/null
+++ b/test/function_view.cpp
@@ -0,0 +1,123 @@
+#include <function2/function2.hpp>
+#include <stdplus/function_view.hpp>
+
+#include <functional>
+
+#include <gtest/gtest.h>
+
+namespace stdplus
+{
+
+int add(int a, int b)
+{
+    return a + b;
+}
+
+int mul(int a, int b) noexcept
+{
+    return a * b;
+}
+
+struct Funcy
+{
+    int a = 0;
+
+    int operator()(float y) noexcept
+    {
+        return a += (int)y;
+    }
+
+    int operator()(int b) noexcept
+    {
+        return a += b;
+    }
+};
+
+struct FuncyPriv : private Funcy
+{
+    using Funcy::operator();
+};
+
+int callFv(stdplus::function_view<int(int, int)> fv)
+{
+    return fv(3, 5);
+}
+
+TEST(FunctionView, Ptr)
+{
+    function_view<int(int, int)> fv = add;
+    EXPECT_EQ(3, fv(1, 2));
+    EXPECT_EQ(14, fv(5, 9));
+    fv = mul;
+    EXPECT_EQ(14, fv(7, 2));
+    EXPECT_EQ(0, fv(0, 10));
+    function_view fv2 = add;
+    EXPECT_EQ(1, fv2(1, 0));
+
+    EXPECT_EQ(8, callFv(add));
+}
+
+TEST(FunctionView, Obj)
+{
+    Funcy f;
+    function_view<int(int)> fv = f;
+    EXPECT_EQ(2, fv(2));
+    EXPECT_EQ(5, fv(3));
+
+    FuncyPriv fp;
+    fv = fp;
+    EXPECT_EQ(2, fv(2));
+    EXPECT_EQ(5, fv(3));
+}
+
+TEST(FunctionView, Lambda)
+{
+    auto addl = [](int a, int b) { return a + b; };
+    function_view<int(int, int)> fv = addl;
+    EXPECT_EQ(3, fv(1, 2));
+    EXPECT_EQ(14, fv(5, 9));
+    function_view fv2 = addl;
+    EXPECT_EQ(1, fv2(1, 0));
+    fv = fv2;
+    EXPECT_EQ(1, fv(1, 0));
+    function_view<int(int, int) const> fv3 = addl;
+    EXPECT_EQ(1, fv3(1, 0));
+
+    auto mull = [old = 1](int a) mutable { return old *= a; };
+    function_view fvm = mull;
+    EXPECT_EQ(2, fvm(2));
+    EXPECT_EQ(4, fvm(2));
+
+    auto mula = [](auto a, auto b) { return a + b; };
+    fv = mula;
+    EXPECT_EQ(5, fv(3, 2));
+
+    EXPECT_EQ(8, callFv([](auto a, auto b) { return a + b; }));
+}
+
+TEST(FunctionView, StdFunction)
+{
+    std::function<int(int, int)> addf = add;
+    function_view<int(int, int)> fv = addf;
+    EXPECT_EQ(3, fv(1, 2));
+    EXPECT_EQ(14, fv(5, 9));
+    {
+        function_view fv2 = addf;
+        EXPECT_EQ(1, fv2(1, 0));
+        fv = fv2;
+    }
+    EXPECT_EQ(6, fv(3, 3));
+
+    fu2::unique_function<int(int, int) const noexcept> mulf = mul;
+    function_view<int(int, int) const> fv2 = mulf;
+    EXPECT_EQ(2, fv2(1, 2));
+
+    fu2::unique_function<int(int)> mulfa = [old = 1](auto a) mutable {
+        return old *= a;
+    };
+    function_view<int(int)> fva = mulfa;
+    EXPECT_EQ(3, fva(3));
+    EXPECT_EQ(9, fva(3));
+}
+
+} // namespace stdplus
diff --git a/test/meson.build b/test/meson.build
index fd62657..40ac624 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -1,6 +1,7 @@
 gtests = {
   'cancel': [stdplus_dep, gtest_main_dep],
   'exception': [stdplus_dep, gtest_main_dep],
+  'function_view': [stdplus_dep, gtest_main_dep],
   'handle/copyable': [stdplus_dep, gtest_main_dep],
   'handle/managed': [stdplus_dep, gtest_main_dep],
   'hash': [stdplus_dep, gtest_main_dep],