Basic version, for use in a header file:
template<typename Lambda>
bool Func1(int Arg1, Lambda Arg2){ // or Lambda&&, which is usually better
if(Arg1 > 0){
return Arg2(Arg1);
} else {
return false; // remember, all control paths must return a value
}
}
More complex version, if you want to split your interface from your implementation (it has run time costs):
bool Func1(int Arg1, std::function<bool(int)> Arg2){
if(Arg1 > 0){
return Arg2(Arg1);
} else {
return false; // remember, all control paths must return a value
}
}
std::function
uses type erasure to create a custom-created wrapper around your lambda, and then exposes a non-virtual interface that uses the pImpl
pattern to forward it to the custom-created wrapper.1
Or, in less technical terms, std::function<bool(int)>
is a class that can wrap nearly anything that you can call like a function, passing one parameter that is compatible with passing an int
, and it returns something that is compatible with returning a bool
.
A call through a std::function
has a run time cost roughly equal to a virtual
function call (caused by the above type erasure), and when you create it it has to copy the state of the function object (aka functor) passed in (which can be cheap -- stateless lambdas, or lambdas capturing arguments by reference -- or expensive in some other cases) and store it (typically on the free store or heap, which has a cost), while the pure-template versions can be "inlined" at the point of call (ie, can not only cost less than a function call, the compiler can even optimize over the function call and return boundaries!)
If you want to split interface/implementation without all of the runtime costs of std::function
, you can roll your own function_ref (in c++17, because that cuts down on some boilerplate):
template<class Sig>
struct function_ref;
template<class R, class...Args>
struct function_ref<R(Args...)> {
R operator()(Args...args) const {
return pf(state, std::forward<Args>(args)...);
}
function_ref()=default;
function_ref(function_ref const&)=default;
function_ref& operator=(function_ref const&)=default;
explicit operator bool()const{ return pf!=nullptr; }
// this overload reduces indirection by 1 step
// and allows function_ref<Sig> to resolve overloads
// on an overload set sometimes.
function_ref( R(*f)(Args...) ):
pf([](State const& state, Args&&...args)->R{
return reinterpret_cast<R(*)(Args...)>(state.pfunstate)(std::forward<Args>(args)...);
})
{
state.pfunstate = reinterpret_cast<void(*)()>(f);
}
// this grabs anything callable (that isn't this own type)
// and stores a pointer to it to call later.
template<class F>
requires (
std::is_convertible_v<
std::invoke_result_t< std::remove_reference_t<F>, Args... >, R
>
&& !std::is_same_v< std::decay_t<F>, function_ref >
)
function_ref( F&& f ):
pf([](State const& state, Args&&...args)->R{
return (*(std::remove_reference_t<F>*)state.pstate)(std::forward<Args>(args)...);
})
{
state.pstate = std::addressof(f);
}
private:
union State {
void* pstate = nullptr;
void(*pfunstate)();
};
State state;
R(*pf)(State const&, Args&&...) = nullptr;
};
// a deduction guide permitting function_ref{foo} to work
// if foo is a non-overloaded function name.
template<class R, class...Args>
function_ref( R(*)(Args...) )->function_ref<R(Args...)>;
Live example.
This removes the need to ever do any allocation from std::function
by removing ownership semantics from it and just type-erasing calling.
A fancy version of the first example that also handles some corner cases a tad better: (also must be implemented within a header file, or in the same translation unit as it is used)
template<typename Lambda>
bool Func1(int Arg1, Lambda&& Arg2){
if(Arg1 > 0){
return std::forward<Lambda>(Arg2)(Arg1);
} else {
return false; // remember, all control paths must return a value
}
}
which uses a technique known as "perfect forwarding". For some functors, this generates slightly different behavior than #1 (and usually more correct behavior).
Most of the improvement comes form the use of &&
in the argument list: this means that a reference to the functor is passed in (instead of a copy), saving some costs, and allows both a const
or non-const
functor to be passed in.
The std::forward<Lambda>(...)
change would only cause a change in behavior if someone used a relatively new C++ feature that allows methods (including operator()
) to override on the rvalue/lvalue status of the this
pointer. In theory, this could be useful, but the number of functors I've seen that actually override based on the rvalue status of this
is 0
. When I'm writing serious library code (tm) I go to this bother, but rarely otherwise.
There is one more possible thing to consider. Suppose you want to take either a function that returns bool
, or a function that returns void
, and if the function returns void
you want to treat it as if it returned true
. As an example, you are taking a function that is being called when iterating over some collection, and you want to optionally support early halting. The function returns false
when it wants to stop prematurely, and true
or void
otherwise.
Or, in a more general case, if you have multiple overrides of a function, one of which takes a function and others take some other type at the same location.
This is possible, which is as far as I'm going to get into here (either with a smart adapter, or via SFINAE techniques). However, you are probably better off just creating two different named functions, because the techniques required are way too heavy weight.
1 Technically std::function
could use magic fairy dust to do what it does, as its behavior is described by the standard, and not its implementation. I'm describing a simple implementation that approximates the behavior of the std::function
implementation I have interacted with.