I'm trying to craft a function that returns a non-capturing lambda (so it can be converted to a function pointer) that decorates an inner function, where the inner function can be either a lambda or a member function pointer.
Compiler Explorer link for reference, which I'll dissect in the following.
I've come up with two strategies, one for lambdas and the other for member functions. Specifically, for lambdas
template <typename Callable>
auto decorate_lambda(Callable&& lambda) {
static const Callable callable = std::forward<Callable>(lambda);
return [](auto... args) {
return 100 + callable(std::forward<decltype(args)>(args)...);
};
}
Saving off the lambda as a static
allows callable
to be used in a non-capturing lambda. This is fine because the decorate_lambda
template instantiation will be unique for each lambda (IIRC).
For member functions the strategy is somewhat different (note the template parameter)
template <auto callable>
auto decorate_memfn() {
return [](auto... args) {
return 100 + std::mem_fn(callable)(std::forward<decltype(args)>(args)...);
};
}
I can then do something like
const auto decorated_lambda =
decorate_lambda([](int i) { return i + 1; });
const auto decorated_memfn =
decorate_memfn<&Worker::work>();
int (*lambda_fnptr)(int) = decorated_lambda;
int (*memfn_fnptr)(Worker&, int) = decorated_memfn;
resulting in function pointers that can be used e.g. in a C interface (ultimately).
The end result that I would like is to roll decorate_lambda
and decorate_memfn
into a single decorate(lambda_or_memfn)
function (using template specialisation, if constexpr
, or whatever). E.g.
decorate([](int i) { return i + 1; });
decorate(&Worker::work);
I.e. essentially I would like to have decorate_memfn(&Worker::work)
rather than decorate_memfn<&Worker::work>()
. The problem is that
decorate_memfn
. Is there a way to force the compiler to recognize that a parameter is from a static/global and hence allow it's use within a lambda without capturing?decorate_lambda
with it's static
trick doesn't work for member function pointers because the template instantiation is not necessarily unique (i.e. if two Callable
s have the same signature). Maybe there is a way to make it unique, though?I understand C++20 could help, but unfortunately I'm stuck with C++17.
Any hints much appreciated!
First, your decorate_lambda
is buggy: it silently breaks if you call it with a stateful callable. As a simple check, you could allow callables only if std::is_empty_v
is true.
The end result that I would like is to roll decorate_lambda and decorate_memfn into a single decorate(lambda_or_memfn) function
You can use std::integral_constant
template<auto x>
inline constexpr std::integral_constant<decltype(x), x> constant{};
template<typename T, typename F, F T::* x>
auto decorate(std::integral_constant<F T::*, x>)
{
return [](auto&&... args) {
return 100 + std::mem_fn(x)(decltype(args)(args)...);
};
}
auto d = decorate(constant<&Worker::work>);