Concept Subspace :: sus :: fn :: Fn
template <class F, class... S>concept Fn
requires {
// Receives and passes along the signature as a pack instead of a single
// argument in order to consistently provide a static_assert() in `Sig` when
// `S` is not a function signature.
{
__private::InvokedFn<F, typename __private::Sig<S...>::Args>::returns()
} -> __private::ValidReturnType<typename __private::Sig<S...>::Return>;
requires FnMut<F, S...>;
requires FnOnce<F, S...>;
}
The version of a callable object that may be called multiple times without mutating internal state.
A Fn
is useful for a callable that is expected to be called one or more
times and whose results do not change between calls. This is of course
possible to violate with mutable
or global state, but it is discouraged
as it violates the Fn
protocol expectations of the caller.
FnMut
should be used when the function will
mutate anything and can return different values as a result.
A type that satisfies Fn
will return a type that can be converted to
R
when called with the arguments Args...
. Fn
is satisfied by
being callable as a const lvalue (which is done by providing an operator()
that is const
- or const&
-qualified). Const lambdas will satisfy
Fn
but mutable ones will not.
The second argument of Fn<F, S>
is a function signature with the format
ReturnType(Args...)
, where Args...
are the arguments that will be passed
to the Fn
and ReturnType
is what is expected to be received back. It
would appear as a matching concept as:
void function(Fn<ReturnType(Args...)> auto f) { ... }
Use of Fn
Fn
should be received by value typically, but can also be received as a
const reference.
A Fn
should be called by passing it to call
along
with any arguments.
This ensures the correct overload is called on the object and
that method pointers are called correctly.
A Fn
may be called any number of times, unlike FnOnce
, and should not
be moved when called.
Type erasure
Using a concept like Fn
in a function parameter requires the function
to be a template. Template functions can not be virtual, they must appear
in the header, and they can have a negative impact on binary size. So it can
be desirable to work with a Fn
without templates.
To do so, Fn
supports being type-erased, on the heap or the stack,
into a DynFn
type.
To receive ownership of a type-erased Fn
, receive a
Box
<
DynFn<R(Args...)>
>
instead.
To receive a reference to a type-erased Fn
, receive a
DynFn<R(Args...)>&&
instead.
See DynConcept
for more on type erasure of
concept-satisfying types.
Compatibility
Any callable type that satisfies Fn
will also satisfy FnMut
and
FnOnce
. A Fn
may be called multiple times, or a single time, which is
compatible with both FnMut
and FnOnce
. And while FnMut
and FnOnce
are able to mutate state when run, they are not required to and a constant
Fn
satisfies them.
Examples
A function that receives a Fn
matching type and calls it:
// Accepts any type that can be called once with (Option<i32>) and returns
// i32.
static i32 do_stuff(sus::fn::Fn<i32(sus::Option<i32>)> auto f) {
return sus::fn::call(f, sus::some(400)) +
sus::fn::call(f, sus::some(100));
}
i32 x = do_stuff([i = 1_i32](sus::Option<i32> o) -> i32 {
return sus::move(o).unwrap_or_default() + i;
});
sus_check(x == 401 + 101);
A Fn
whose first parameter is a class can be matched with a method from
that same class if the remaining parameters match the method's signature:
struct Class {
Class(i32 value) : value_(value) {}
i32 value() const { return value_; }
private:
i32 value_;
};
i32 map_class(const Class& c,
sus::fn::Fn<i32(const Class&)> auto const& f) {
return sus::fn::call(f, c);
}
// Map the class C to its value().
auto c = Class(42);
sus_check(map_class(c, &Class::value) == 42);
Using a method pointer as the parameter for Option::map()
will call that
method on the object inside the Option:
struct Class {
Class(i32 value) : value_(value) {}
i32 value() const { return value_; }
private:
i32 value_;
};
auto o = sus::Option<Class>(Class(42));
sus_check(o.map(&Class::value) == sus::some(42));