Reputation: 2853
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
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
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 {};
}
}
Upvotes: 9