5

I want to create some template which essentially should wrap it's parameter. The parameter should be an arbitrary function call, which gets wrapped via some template metaprogramming magic with prefix and postfix code.

I want to use it like follows:

auto result = try_call( some_vector.at(13) );

and try_call would be defined somehow that it wraps a try..catch block around some_vector.at(13). Something like this:

template<typename T>
// some template metaprogramming magic here
try {
    auto value = // execute the parameter here, i.e. some_vector.at(13);
    return std::experimental::optional<T>(value);
} 
catch (std::exception&) {
    return std::experimental::nullopt;
}

There is that paper of Bjarne Stroustrup, but that's not exactly describing what I need, and I wasn't able to find a solution to this problem.

If this isn't possible directly, I am currently thinking of doing it via a templated function taking a lambda:

template<typename Func>
auto try_call(Func f) {
    try {
        return f();
    } catch(std::exception&) {
        return std::experimental::nullopt;
    }
}

But I don't know if that's a good idea. The lambda has some overhead, I guess? I want to avoid any unneccessary overhead.

j00hi
  • 5,420
  • 3
  • 45
  • 82
  • 1
    "The lambda has some overhead, I guess?" Why do you say this? A lambda is a great solution for this problem. – Aaron McDaid Jul 21 '15 at 07:39
  • Well, I thought there might be some overhead compared to a plain function call. I mean, there is an overhead with `std::function` for sure, since a functor is stamped out. I know that lambdas are more efficient, but can I be sure about that? Is a lambda as cheap as calling a method directly? – j00hi Jul 21 '15 at 10:21
  • 3
    [Lambdas can be entirely inlined](http://stackoverflow.com/a/13722515/1968), in which case they have *no* overhead. And at any rate the point is moot because you cannot implement this without lambdas (or equivalent) — short of using macros, potentially. – Konrad Rudolph Jul 21 '15 at 10:26

2 Answers2

5

Actually, your solution with a lambda is quite good and efficient. From a type theoretic point of view, try_call is a higher order function: It takes as argument another function and executes it in a try catch context.

template<typename Func>
auto try_call(Func f) -> std::experimental::optional<std::decay_t<decltype(f())>> {
    try {
        return std::experimental::make_optional(f());
    } catch(std::exception&) {
        return std::experimental::nullopt;
    }
}

Calling it with a lambda will yield what you want without any overhead. A lambda is compiled to an anonymous struct with an overloaded function call operator. This struct is used as template parameter for your try_call function. Therefore, the compiler knows exactly the function to be executed when calling f() and it will be inlined. No overhead involved.

T.C.
  • 133,968
  • 17
  • 288
  • 421
gexicide
  • 38,535
  • 21
  • 92
  • 152
0

I played around with this; and, this is what I came up with for a possible solution.

// Function
template<typename ReturnType, typename... Args, typename... UArgs>
ReturnType no_throw_call(ReturnType (*f)(Args...), UArgs... args)
{
    try { return f(args...); }
    catch (...) {}
    return ReturnType();
}

// Method
template<typename ReturnType, typename Class, typename... Args, typename... UArgs>
ReturnType no_throw_call_method(Class &c, ReturnType(Class::*m)(Args...), UArgs... args)
{
    try { return (c.*m)(args...); }
    catch (...) {}
    return ReturnType();
}

note: using the UArgs allows for automatic conversion of types if the types don't exactly match the signature of the function when there is an allowable conversion between types.

Also, this code guarantees returning a default initialized value.

I would suggest that if this type of code is used, that you should include some sort of error logging within the catch block. This might hide your bugs from your customers, but you don't want to hide your bugs from your developers and QA.

In fact, I would suggest having a debug version of this to actually allow your program to crash like it should have done during QA testing. Otherwise, your customers may experience some bad undefined behavior because you failed to find your bugs because you hid them.

Example Usage:

#define nt_strcpy(X,Y) no_throw_call(strcpy, (X), (Y))
nt_strcpy(nullptr, nullptr);

B b;
// this calls a method named do_it which takes an int as a parameter and returns void
no_throw_call_method<void>(b, &B::do_it, 1);
Clash
  • 81
  • 7
  • To catch exceptions from the c-runtime library which would throw from a call like strcpy(nullptr,nullptr), compile with /EHa – Clash Jan 28 '16 at 14:39
  • You only need to specify the ReturnType in the template if B::do_it is overloaded to return different types for different signatures: for example: void do_it(int i); int do_it(); – Clash Jan 28 '16 at 15:10