1

I have functions like:

template <typename T>
Result<T> run(std::function<T()> f);

and with various templated parameters. Like:

template <typename T, typename P>
Result<T> run(std::function<T(P)> f):

There are several layers of these that call each other and add features like retry. Rather than write a version of those for every instance, those look like:

template <typename F>
auto runWithRetry(F f);

The problems start when I pass a lambda to one of these. When I call run directly I can specify the template parameters and then the lambda gets converted to a std::function. I don't want to specify the template parameters manually, but it works.

When calling the wrappers, it gets more complicated since I'd have to manually specify an std::function template parameter. Otherwise, the lambda arrives at run and doesn't match because it's not a std::function and, as I understand now, conversion happens later.

I'd like the whole thing to work without manually specifying any template parameters. I'd like the types to be deduced.

How can I create something like that? Or, what's the right way for a function template to specify a callable parameter?

I'm using C++20.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
Hesky Fisher
  • 1,145
  • 7
  • 14
  • 1
    The last variant (`template auto runWithRetry(F f);`) if what the standard library itself uses for all callable objects. It can be used for anything that's callable, including lambdas, function objects, functions, static member functions, `std::function` instances, and `std::bind` objects. I.e. everything that can be called. – Some programmer dude May 18 '23 at 16:31
  • Usually your last one is enough to capture std::function and lambdas. I find accepting lambdas is usually enough because lambdas can do the heavy lifting of capturing variables etc. – Pepijn Kramer May 18 '23 at 16:31
  • how can I provide some type safety or sane error messages when just accepting a single type for the callable? – Hesky Fisher May 18 '23 at 16:54
  • 2
    @HeskyFisher Show the calling code also. A [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) instead of describing the problem in words. –  May 18 '23 at 17:01
  • 1
    Check [this answer](https://stackoverflow.com/a/24068396/2567683) if you want to convert lambdas to `std::function` without hassle. In general the "single template parameter" is the canonical way of passing a callable since it can leverage all types of callables. A good addition to that is using `std::invoke` since it (1) unifies the calling syntax and (2) already incorporates concept checking for the combination of arguments and call. Check [this answer](https://stackoverflow.com/a/21995693/2567683) - section 2 "Instrumentation" on how a callable is passed to `Measure` – Nikos Athanasiou May 18 '23 at 19:17

2 Answers2

0
template<class Sig>
struct sig_retval;
template<class R, class...Args>
struct sig_retval<R(Args...)> {
  using type=R;
};
template<Sig>
using sig_retval_t = typename sig_retval<Sig>::type;

template<class Sig>
Result<sig_retval_t<Sig>> run( std::function<Sig> f );

You can also avoid type erasure by writing a call_compatible<F, Sig> which checks that if you call F with the arguments of Sig, you get a result convertible-to the return type of Sig.

template<class Sig>
Result<sig_retval_t<Sig>> run( call_compatible<Sig> auto f );

which checks f but doesn't force type erasure.

In general, you cannot map a callable object into a fixed signature or even find its return value without stating what arguments you'd pass it, because overloading is a thing in C++.

call_compatible is a bit fun.

template<class F, class Sig>
struct call_compatible_helper;

template<class F, class R, class...Args>
struct call_compatible_helper<F, R(Args...)>:
  std::is_convertible< std::invoke_result_t<F, Args...>, R >
{};
template<class F, class...Args>
struct call_compatible_helper<F, void(Args...)>:
  std::is_invocable<F, Args...>
{};
template<class F, class Sig>
concept call_compatible = requires call_compatible_helper<T,Sig>::value;

or something similar (there are probably tpyos).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

Since C++17, you can use class template argument deduction (CTAD) for std::function. That is, if you write std::function{f} without specifying the template argument for std::function, the template argument can be automatically deduced from f's operator(), using the deduction guide for std::function.

Of course, if you pass a generic lambda, then it's impossible to deduce the expected type, in which case you have to specify it manually.

cpplearner
  • 13,776
  • 2
  • 47
  • 72