/* 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 */