/* Copyright (C) 2017-2024 Free Software Foundation, Inc. This file is part of GDB. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef GDBSUPPORT_FUNCTION_VIEW_H #define GDBSUPPORT_FUNCTION_VIEW_H /* function_view is a polymorphic type-erasing wrapper class that encapsulates a non-owning reference to arbitrary callable objects. A way to put it is that function_view is to std::function like std::string_view is to std::string. While std::function stores a type-erased callable object internally, function_view holds a type-erased reference to an external callable object. This is meant to be used as callback type of a function that: #1 - Takes a callback as parameter. #2 - Wants to support arbitrary callable objects as callback type (e.g., stateful function objects, lambda closures, free functions). #3 - Does not store the callback anywhere; instead the function just calls the callback directly or forwards it to some other function that calls it. #4 - Can't be, or we don't want it to be, a template function with the callable type as template parameter. For example, when the callback is a parameter of a virtual member function, or when putting the function template in a header would expose too much implementation detail. Note that the C-style "function pointer" + "void *data" callback parameter idiom fails requirement #2 above. Please don't add new uses of that idiom. I.e., something like this wouldn't work; typedef bool (iterate_over_foos_cb) (foo *f, void *user_data), void iterate_over_foos (iterate_over_foos_cb *callback, void *user_data); foo *find_foo_by_type (int type) { foo *found = nullptr; iterate_over_foos ([&] (foo *f, void *data) { if (foo->type == type) { found = foo; return true; // stop iterating } return false; // continue iterating }, NULL); return found; } The above wouldn't compile, because lambdas with captures can't be implicitly converted to a function pointer (because a capture means some context data must be passed to the lambda somehow). C++11 gave us std::function as type-erased wrapper around arbitrary callables, however, std::function is not an ideal fit for transient callbacks such as the use case above. For this use case, which is quite pervasive, a function_view is a better choice, because while function_view is light and does not require any heap allocation, std::function is a heavy-weight object with value semantics that generally requires a heap allocation on construction/assignment of the target callable. In addition, while it is possible to use std::function in such a way that avoids most of the overhead by making sure to only construct it with callables of types that fit std::function's small object optimization, such as function pointers and std::reference_wrapper callables, that is quite inconvenient in practice, because restricting to free-function callables would imply no state/capture/closure, which we need in most cases, and std::reference_wrapper implies remembering to use std::ref/std::cref where the callable is constructed, with the added inconvenience that std::ref/std::cref have deleted rvalue-ref overloads, meaning you can't use unnamed/temporary lambdas with them. Note that because function_view is a non-owning view of a callable, care must be taken to ensure that the callable outlives the function_view that calls it. This is not really a problem for the use case function_view is intended for, such as passing a temporary function object / lambda to a function that accepts a callback, because in those cases, the temporary is guaranteed to be live until the called function returns. Calling a function_view with no associated target is undefined, unlike with std::function, which throws std::bad_function_call. This is by design, to avoid the otherwise necessary NULL check in function_view::operator(). Since function_view objects are small (a pair of pointers), they should generally be passed around by value. Usage: Given this function that accepts a callback: void iterate_over_foos (gdb::function_view callback) { for (auto &foo : foos) callback (&foo); } you can call it like this, passing a lambda as callback: iterate_over_foos ([&] (foo *f) { process_one_foo (f); }); or like this, passing a function object as callback: struct function_object { void operator() (foo *f) { if (s->check ()) process_one_foo (f); } // some state state *s; }; state mystate; function_object matcher {&mystate}; iterate_over_foos (matcher); or like this, passing a function pointer as callback: iterate_over_foos (process_one_foo); There's also a gdb::make_function_view function that you can use to automatically create a function_view from a callable without having to specify the function_view's template parameter. E.g.: auto lambda = [&] (int) { ... }; auto fv = gdb::make_function_view (lambda); This can be useful for example when calling a template function whose function_view parameter type depends on the function's template parameters. In such case, you can't rely on implicit callable->function_view conversion for the function_view argument. You must pass a function_view argument already of the right type to the template function. E.g., with this: template void my_function (T v, gdb::function_view callback = nullptr); this wouldn't compile: auto lambda = [&] (int) { ... }; my_function (1, lambda); Note that this immediately dangles the temporary lambda object: gdb::function_view fv = [&] (int) { ... }; // dangles my_function (fv); To avoid the dangling you'd have to use a named temporary for the lambda: auto lambda = [&] (int) { ... }; gdb::function_view fv = lambda; my_function (fv); Using gdb::make_function_view instead automatically deduces the function_view's full type, and, avoids worrying about dangling. For the example above, we could write instead: auto lambda = [&] (int) { ... }; my_function (1, gdb::make_function_view (lambda)); You can find unit tests covering the whole API in unittests/function-view-selftests.c. */ #include namespace gdb { namespace fv_detail { /* Bits shared by all function_view instantiations that do not depend on the template parameters. */ /* Storage for the erased callable. This is a union in order to be able to save both a function object (data) pointer or a function pointer without triggering undefined behavior. */ union erased_callable { /* For function objects. */ void *data; /* For function pointers. */ void (*fn) (); }; } /* namespace fv_detail */ /* Use partial specialization to get access to the callable's signature. */ template struct function_view; template class function_view { template using CompatibleReturnType = Or, std::is_same, std::is_convertible>; /* True if Func can be called with Args, and either the result is Res, convertible to Res or Res is void. */ template::type> struct IsCompatibleCallable : CompatibleReturnType {}; /* True if Callable is a function_view. Used to avoid hijacking the copy ctor. */ template struct IsFunctionView : std::is_same::type> {}; public: /* NULL by default. */ constexpr function_view () noexcept : m_erased_callable {}, m_invoker {} {} /* Default copy/assignment is fine. */ function_view (const function_view &) = default; function_view &operator= (const function_view &) = default; /* This is the main entry point. Use SFINAE to avoid hijacking the copy constructor and to ensure that the target type is compatible. */ template >>, typename = Requires>> function_view (Callable &&callable) noexcept { bind (callable); } /* Construct a NULL function_view. */ constexpr function_view (std::nullptr_t) noexcept : m_erased_callable {}, m_invoker {} {} /* Clear a function_view. */ function_view &operator= (std::nullptr_t) noexcept { m_invoker = nullptr; return *this; } /* Return true if the wrapper has a target, false otherwise. Note we check M_INVOKER instead of M_ERASED_CALLABLE because we don't know which member of the union is active right now. */ constexpr explicit operator bool () const noexcept { return m_invoker != nullptr; } /* Call the callable. */ Res operator () (Args... args) const { return m_invoker (m_erased_callable, std::forward (args)...); } private: /* Bind this function_view to a compatible function object reference. */ template void bind (Callable &callable) noexcept { m_erased_callable.data = (void *) std::addressof (callable); m_invoker = [] (fv_detail::erased_callable ecall, Args... args) noexcept (noexcept (callable (std::forward (args)...))) -> Res { auto &restored_callable = *static_cast (ecall.data); /* The explicit cast to Res avoids a compile error when Res is void and the callable returns non-void. */ return (Res) restored_callable (std::forward (args)...); }; } /* Bind this function_view to a compatible function pointer. Making this a separate function allows avoiding one indirection, by storing the function pointer directly in the storage, instead of a pointer to pointer. erased_callable is then a union in order to avoid storing a function pointer as a data pointer here, which would be undefined. */ template void bind (Res2 (*fn) (Args2...)) noexcept { m_erased_callable.fn = reinterpret_cast (fn); m_invoker = [] (fv_detail::erased_callable ecall, Args... args) noexcept (noexcept (fn (std::forward (args)...))) -> Res { auto restored_fn = reinterpret_cast (ecall.fn); /* The explicit cast to Res avoids a compile error when Res is void and the callable returns non-void. */ return (Res) restored_fn (std::forward (args)...); }; } /* Storage for the erased callable. */ fv_detail::erased_callable m_erased_callable; /* The invoker. This is set to a capture-less lambda by one of the 'bind' overloads. The lambda restores the right type of the callable (which is passed as first argument), and forwards the args. */ Res (*m_invoker) (fv_detail::erased_callable, Args...); }; /* Allow comparison with NULL. Defer the work to the in-class operator bool implementation. */ template constexpr inline bool operator== (const function_view &f, std::nullptr_t) noexcept { return !static_cast (f); } template constexpr inline bool operator== (std::nullptr_t, const function_view &f) noexcept { return !static_cast (f); } template constexpr inline bool operator!= (const function_view &f, std::nullptr_t) noexcept { return static_cast (f); } template constexpr inline bool operator!= (std::nullptr_t, const function_view &f) noexcept { return static_cast (f); } namespace fv_detail { /* Helper traits type to automatically find the right function_view type for a callable. */ /* Use partial specialization to get access to the callable's signature, for all the different callable variants. */ template struct function_view_traits; /* Main partial specialization with plain function signature type. All others end up redirected here. */ template struct function_view_traits { using type = gdb::function_view; }; /* Function pointers. */ template struct function_view_traits : function_view_traits { }; /* Function references. */ template struct function_view_traits : function_view_traits { }; /* Reference to function pointers. */ template struct function_view_traits : function_view_traits { }; /* Reference to const function pointers. */ template struct function_view_traits : function_view_traits { }; /* Const member functions. function_view doesn't support these, but we need this in order to extract the type of function objects. Lambdas pass here, after starting at the operator() case, below. */ template struct function_view_traits : function_view_traits { }; /* Member functions. Ditto, for function objects with non-const operator(). */ template struct function_view_traits : function_view_traits { }; /* Function objects, lambdas, std::function, any type that defines operator(). */ template struct function_view_traits : function_view_traits ::type::operator())> { }; } /* namespace fv_detail */ /* Make a function_view from a callable. Useful to automatically deduce the function_view's template argument type. */ template auto make_function_view (Callable &&callable) -> typename fv_detail::function_view_traits::type { using fv = typename fv_detail::function_view_traits::type; return fv (std::forward (callable)); } } /* namespace gdb */ #endif /* GDBSUPPORT_FUNCTION_VIEW_H */