Inline
Inline

Reputation: 2853

User-defined deduction guide for lambda

today I'm struggling with C++ templates. This is my simple code for convenient exception handling in JNI code.

template<typename T>
std::optional<T> handleNativeCrash(JNIEnv *env, std::function<T(void)> f) {
    try {
        return std::optional<T>(f());
    }
    catch (const std::exception &e) {
        jniThrowException(env, e.what());
        return {};
    }
}

When I try to use it without specifying T, Clang fails roughly with this error

no matching function for call to 'handleNativeCrash'
      return my_namespace::handleNativeCrash(env, [&]{
             ^~~~~~~~~~~~~~~~~~~~~
  /......../jni.cpp:116:39)'
      std::optional<T> handleNativeCrash(JNIEnv *env, std::function<T(void)> f) {
                       ^
  1 error generated.

I would like to infer T automatically from lambda return type. I tried to write deduction guide, but it seems that I'm unable to write it for global function. I tried to create simple template struct which contained only this function, but I failed too. It seems I don't really understand C++ template metaprogramming.

My first attempt was like this, but Clang just crashed complaining about illegal syntax and printing it's backtrace. I will report bug soon, but I need to finish this job first.

template<typename T>
handleNativeCrash(JNIEnv *env, std::function<T(void)> f) -> handleNativeCrash<decltype(f())>;

How can I achieve my goal?

Upvotes: 4

Views: 777

Answers (2)

max66
max66

Reputation: 66220

The problem is that a lambda can be converted to a std::function but isn't a std::function.

So the T type can't be deduced because you don't have a std::function and a std::function can't be obtained because you don't know T.

A sort of chicken and egg problem.

So you have to pass a std::function to handleNativeCrash()

return my_namespace::handleNativeCrash(env, std::function{[&]{/*...*/}});

or you have to receive a generic callable as in YSC answer or, by example, something as follows (thanks to Holt for pointing my original error):

template <typename R = void, typename F,
          typename T = std::conditional_t<std::is_same_v<void, R>,
                          decltype(std::declval<F>()()), R>>
std::optional<T> handleNativeCrash(JNIEnv *env, F f) {
    try {
        return std::optional<T>(f());
    }
    catch (const std::exception &e) {
        jniThrowException(env, e.what());
        return {};
    }
}

The YSC solution (detecting returned type using decltype() in trailing return type) impose the type returned by the functional; using additionals (with default values) template parameters permit also to "hijack" the returned type explicating it.

Suppose you have a l lambda that return an int but you want a std::optional<long>, you can write something as

return my_namespace::handleNativeCrash<long>(env, l);

Upvotes: 0

YSC
YSC

Reputation: 40100

You cannot use template deduction for that, it's not smart enough and only works on matching.

But you can manually infer it:

template<class Callable>
auto handleNativeCrash(JNIEnv *env, Callable f)
-> std::optional<decltype(f())>
{
    try {
        return f();
    }
    catch (const std::exception &e) {
        jniThrowException(env, e.what());
        return {};
    }
}

Simplified live demo

Upvotes: 9

Related Questions