Reputation: 85531
At CppCon 2015, S. Lavavej from Microsoft said to avoid using result_of
. But I have a situation in which I can't seem to be able to find a proper alternative.
Consider the following code. Is there any way to change std::result_of<F()>::type
to use decltype
instead?
#include <iostream>
#include <future>
#include <type_traits>
template<typename T>
struct NopErrCB
{
constexpr T operator() () const { return T(); }
};
template <typename Func, typename ErrCB>
struct SafeTaskWrapper {
Func f;
ErrCB errCB;
template <typename... T>
auto operator()(T&&... args) -> decltype(f(args...)) {
try
{
return f(std::forward<T>(args)...);
}
catch (...)
{
return errCB();
}
}
};
// vvv OVER HERE vvv
template <typename F>
SafeTaskWrapper<F, NopErrCB<typename std::result_of<F()>::type>> make_safe_task(F&& f) {
return { std::forward<F>(f) };
}
template <typename F, typename ErrCB>
SafeTaskWrapper<F, ErrCB> make_safe_task(F&& f, ErrCB&& cb) {
return { std::forward<F>(f), std::forward<ErrCB>(cb) };
}
int main() {
auto futA = std::async(std::launch::async, make_safe_task([] { throw "A"; return 1; }));
auto futB = std::async(std::launch::async, make_safe_task([] { throw "B"; return 1; }, [] { return 2; }));
auto futC = std::async(std::launch::async, make_safe_task([] { throw "C"; }));
std::cout << futA.get() << std::endl;
std::cout << futB.get() << std::endl;
futC.get();
}
PS. Please don't mind the purpose of the SafeTaskWrapper. I know a future already handles C++ exceptions. This is just a demo, the actual code is for handling Windows SEH exceptions, but that doesn't matter for this question.
Upvotes: 4
Views: 920
Reputation: 218268
You might use
decltype(std::declval<F>()())
instead of
typename std::result_of<F()>::type
which are similar, but have subtle differences
Upvotes: 4
Reputation: 275896
Yes, your use of result_of
here causes a bug. And yes, there is a way to write your code without result_of
that clears the bug. And we can do this without constructing an equivalently buggy decltype
replacement.
First, note your result_of
code has a bug.
struct evil {
template<class...Args>
int operator()(Args&&)&&{return 0;}
template<class...Args>
std::string operator()(Args&&)const&{return "hello";}
};
Pass an evil{}
to your code, and it will fail to compile. That is because you evaluated f
in SafeTaskWrapper
in an lvalue context, while you calculated result_of
in an rvalue context.
This is exactly the kind of error that S.T.L. was talking about.
Now, std::result_of_t<F(Args...)>
is basically1 equivalent to decltype( std::declval<F>()( std::declval<Args>()... ) )
, or in this case decltype( std::declval<F>()())
. Replacing result_of_t
with decltype
in this sense is utterly useless.
What I'd do here is I'd change the rest of your code.
struct anything_really {
template<class T> operator T() const { return {}; }
};
struct return_anything {
anything_really operator()() const { return {}; }
};
this replaces your errCB
type. Note that it isn't a template type.
Next, we have to handle the "don't return into void" problem. This does it:
template<class R>
struct smart_invoke {
template<class F>
R operator()(F&& f)const {
return std::forward<F>(f)();
}
};
template<>
struct smart_invoke<void> {
template<class F>
void operator()(F&& f)const {
std::forward<F>(f)();
}
};
Which gives us our final solution:
template <typename Func, typename ErrCB=return_anything>
struct SafeTaskWrapper {
Func f;
ErrCB errCB;
template <class... Ts>
decltype(auto) operator()(Ts&&... args) {
try {
return f(std::forward<T>(args)...);
} catch (...) {
using R=decltype( f( std::forward<T>(args)... ) );
return smart_invoke<R>{}(errCB);
}
}
};
Now our factory functions. I could keep two, but I'm lazy.
template <class ErrCB=return_anything, class F>
SafeTaskWrapper<F, ErrCB> make_safe_task(F&& f, ErrCB&& cb = {}) {
return { std::forward<F>(f), std::forward<ErrCB>(cb) };
}
I also swapped the template argument order, so you can pass in a different default-constructible ErrCB
and not bother passing in a value while I was at it.
And result_of_t
has been eliminated.
Also your trailing return decltype had an error in it. It should be exactly the same as the (key) return statement, not similar.
1 There are exceptions if F
is a member function pointer or member or the like, but your code won't work in those cases anyhow. In C++17, you'll want to call std::invoke
in your operator()
and in the decltype
return type clause as well. The other changes remain correct, and because we avoid deducing the return type in two spots, they remain unchanged.
Upvotes: 4
Reputation: 69912
The direct translation would be:
template <typename F>
SafeTaskWrapper<F, NopErrCB<decltype(std::declval<F>()())>> make_safe_task(F&& f) {
return { std::forward<F>(f) };
}
Which seems no clearer to me.
Upvotes: 1
Reputation: 10427
That's where std::result_of
is used. I think that is enough.
If you want shorter and more readable code, consider using std::result_of_t
, which is added since C++14.
//NopErrCB<typename std::result_of<F()>::type>
NopErrCB<std::result_of_t<F()>> // equal
Upvotes: 2