Any interest in trap_exceptions functionality?

Hi, I often find myself wanting a generic way to catch and process exceptions. For example, imaging having to export "C" interface from a C++ library. You would create a bunch of extern "C" functions that would invoke the right C++ code. One of the things that needs to be done in every such function is to catch and process all exceptions. Most of the exceptions-handling code would look the same or very similar (the same list of exceptions to catch, same way to handle given exception types). C++ to C is not the only place where you'd need this. Most of the module boundaries might want to trap exceptions. The first thing that developers do in these cases is they start generalizing the handling part. I.e., you'd create a separate function/functor that would handle an exception of a certain kind. Unfortunately it still leaves you with a barage of try/catch() statements in every function that doesn't want to leak exceptions. This creates not only the unpleasant look of a lot of catches (one might argue this point), but it creates a lot of repetition - you need to create catch statement for every exception you want to handle. It would be nice if I could assemble all the exceptions I want to handle (honoring their order) and the way to handle them in one single place under a single object. And then invoke that object on all functions that need to be called. I recently took a crack at this task and came up with a generic way to trap exception. Here's a brief API: trap_exceptions(<list-of-exception-handlers>); The previous line would create a callable object. The parameters are a variable number of exceptions handlers that could be either function pointers, references to functions or functors. One thing they have in common though is the signature: the signature should be "void(T)", where T is the type of the exception that the handler will handle. This "T" type is what the trap_exceptions-created callable object will use in the "catch" statements. Other important notes: the order of handlers is important as this will be the order of "catch" statements (i.e., trap_exceptions(HandleBase, HandleDerived) will hide all Derived exceptions). Whether the exception is caught by reference or by value is also controlled by the handler's signature. Signature "void()" means catch all exceptions indiscriminately. Specifying an empty parameter list will also mean to catch all exceptions indiscriminately. The result of the trap_exceptions call can be assigned to a variable: auto ex_trap = trap_exceptions(HandleEx1, HandleEx2, HandleEx3); //BOOST_AUTO can be used too. And I think it will be possible to use //it without registering HandleEx1 etc.) This object can now be used on multiple functions of any arity and any return type. Functors are supported too. ex_trap(func1); //will create a delayed-call object that upon invocation will call func1 and catch exceptions Ex1, Ex2 and Ex3 So, if func1 has signature int(int, char*, float), then ex_trap(func1)(1, "abc", .5); will call function func1 with the given set of arguments and catch afore-mentioned exceptions. The return type undergoes a significant transformation though. Since the return from this delayed function call now returns more information than before (the additional part being whether an exception has been thrown or not), this additional information is conveyed through the use of boost::optional. In the case of ex_trap+func1, the result will be boost::optional<int>. Functions/functors returning void have a special treatment though since unfortunately it's illegal to instantiate boost::optional<void>. For functions that return void, the return type of the delayed call will be a simple bool. There are some return types that have a special value that indicates "error" and could be used to indicate that an exception occured. In this case, just for convenience purposes, it'll be possible to specify this value and the return type will become the return type of the inner function, not optional<T>. I.e: //Let's say that -1 can be used to indicate that an exception has //occured, then ex_trap(func1, -1)(1, "abc", .5);//will have a return type of int. //-1 will be returned if an exception has been thrown and now all places that had return func1(1, "abc", .5); could simply be replaced with return ex_trap(func1, -1)(1, "abc", .5); //without using boost::optional I think one other use of this functionality can be in functional-style programming in C++. While it's trivial to convert syntactic constructs like "if", "while", "switch", and "for" to functional representations (i.e. if_(f1(), f2(), f3()); which means if (f1()) f2(); else f3(); ), it's a lot harder to create a functional representation of try/catch construct. trap_exceptions is an attempt at that. To illustrate that point more, say we have a tuple (or a fusion-like container) which contains a collection of functions/functors. Using fusion::transform it's possible to add certain qualities to these functions. With trap_exceptions, it'll be possible to add a "no exception leak" quality to this set of functions. Do you think a functionality like that will be useful for boost? Any comments are appreciated. Thanks, Andy. P.S. Currently I implemented this functionality in terms of C++0x. But I think it should be possible to use Boost to make it C++03 compatible.

Andy Venikov wrote:
I often find myself wanting a generic way to catch and process exceptions.
[snip]
I recently took a crack at this task and came up with a generic way to trap exception.
Here's a brief API:
trap_exceptions(<list-of-exception-handlers>);
The idea is interesting, but I see issues of general applicability. For example, there are relatively few circumstances in which one needs to do much of that sort of exception handling. I don't mean to suggest that it doesn't occur, but I wonder if it occurs often enough to warrant a Boost tool. Another issue is that your approach eliminates access to the calling context. Any handler that needs such access becomes much more complicated. That by no means is required in all circumstances, but it is an issue to consider. Both of those issues can be addressed by a straightforward, ad hoc macro approach. The common handlers can be assembled in a macro invoked in each function needing them. The preprocessor then inserts the code. Is there enough value in your idea to justify a Boost library? Perhaps you'll overcome these issues and prove the value. For example, if trap_exceptions() accepted lambdas, then the calling context would be accessible, at least in C++0x. (Phoenix 3?) As to the general problem you're solving, my solution has always been this: extern "C" void f() { try { // do possibly throwing work here } catch (...) { handle_exception(); } } where handle_exception() looks like this; void handle_exception() { try { throw; } catch (first_type const & e) { // deal with first_type exception } catch (second_type const & e) { // deal with second_type exception } ... catch (...) { // deal with unrecognized exception } } That also suffers from the lack of calling context, but does otherwise serve to fulfill the same need as your solution, except that it isn't composed via function pointer arguments. I have also generalized that scheme in some cases to permit a caller to install a handler function. Thus, in the catch all handler, if a handler function has been installed, I call that to give a last chance to decode and handle the pending exception. That means some types are always handled the same way. The other approach is to have a function like handle_exception() be the default and allow the user to install a replacement function. I've even created a framework whereby the type and message of various exception types can be normalized for logging or translation, including the ability to extend the supported types through virtual functions. The point is there's a lot of room for providing help with exception handling for various purposes, but I'm not certain whether your approach is broadly useful. I'm open to being convinced, however. _____ Rob Stewart robert.stewart@sig.com Software Engineer, Core Software using std::disclaimer; Susquehanna International Group, LLP http://www.sig.com IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

First, thanks a lot for your comments. Stewart, Robert wrote: <snip>
The idea is interesting, but I see issues of general applicability. For example, there are relatively few circumstances in which one needs to do much of that sort of exception handling. I don't mean to suggest that it doesn't occur, but I wonder if it occurs often enough to warrant a Boost tool.
Another issue is that your approach eliminates access to the calling context. Any handler that needs such access becomes much more complicated. That by no means is required in all circumstances, but it is an issue to consider. I think that in most cases the local context is not required, unless the exception handling is done in a mother-of-all-functions, in which case one could have handlers that are aware of the function's context. (What I mean is that if the local context is needed, then the function is
Well, I think it's all relative. I think that this sort of functionality is useful at the boundaries of logically separate modules. How often you deal with these boundaries depends on what kind of applications you usually work with. In my personal experience I think I needed it 4-5 times. Enough to make me bother trying to generalize it. But you're right, I'm sure that there are tons of developers that didn't see a need for this kind of exception handling. probably big and one could spend some time writing functors that know the function's context). Since the trap_exception syntax is really light-weight, it becomes easy to create traps with different set of handlers. Also, as you point out further down yourself, a lambda function could be used. I think C++0x style lambdas should work jsut fine with trap_exceptions since they are not polymorphic. The only lambdas that can be problematic are polymorphic lambdas, since in that case I won't have access to the type of exception that needs to be caught. But I'm sure there should be a way to supply that type elegantly.
Both of those issues can be addressed by a straightforward, ad hoc macro approach. The common handlers can be assembled in a macro invoked in each function needing them. The preprocessor then inserts the code.
Yeah, I'm aware of the macro approach. But what you lose with the macro approach (not mentioning the fact that you have to deal with them :-) ) is the ability to easily create different traps with different sets of exception handlers. I'm sure that a macro guru could probably replicate trap_exceptions functionality using just macros, but then the solution will become probably a lot more complicated. The straight macros won't take exception types as argument and you'll end up creating different macro for every different set of exceptions.
Is there enough value in your idea to justify a Boost library? Perhaps you'll overcome these issues and prove the value. For example, if trap_exceptions() accepted lambdas, then the calling context would be accessible, at least in C++0x. (Phoenix 3?)
I didn't think myself that it should be a separate library. Perhaps a part of utility? Or another library.
As to the general problem you're solving, my solution has always been this:
extern "C" void f() { try { // do possibly throwing work here } catch (...) { handle_exception(); } }
where handle_exception() looks like this;
void handle_exception() { try { throw; } catch (first_type const & e) { // deal with first_type exception } catch (second_type const & e) { // deal with second_type exception } ... catch (...) { // deal with unrecognized exception } }
Again, it's burdensome to create separate handlers for different sets of exceptions and the developers will tend to write one giant handle-them-all handler. Whereas with trap_exceptions you can just compose them in-place. But I gotta admit that for some reason I really underutilized the approach that you just described. It's not that I wasn't aware of it, but somehow it just never came to my mind when considering this kind of exception handling. Maybe some subconscious aversion to the try/catch syntax? <snip>
The point is there's a lot of room for providing help with exception handling for various purposes, but I'm not certain whether your approach is broadly useful. I'm open to being convinced, however. Well, I hope maybe my reply did the job? :-)
Thanks, Andy.

Stewart, Robert wrote: <snip>
The idea is interesting, but I see issues of general applicability. For example, there are relatively few circumstances in which one needs to do much of that sort of exception handling. I don't mean to suggest that it doesn't occur, but I wonder if it occurs often enough to warrant a Boost tool.
Elsethread David Abrahams pointed out that Boost.Python and Boost.Test already have a similar functionality. So, it looks like there has already been some interest. The difference though is that what's there right now is all run-time based. I also feel that I didn't expand enough on the functional-style qualities of trap_exceptions(). Since functional style is not a prevalent style of programming in C++ (but I think it's going to become more and more popular), coming up with a good example is hard. But one could imaging that a piece of software could be broken down into components not as usual with imperative style, but as a set of collections of functions. For example, // #1 BigFunction(args) { //Do functionality1 ....use args //Do functionality2 ....use args //Do functionality3(args) ...use args } Represents and old do-it-all function, which with the advent of modularism can be split into //#2 smallerFunction(args) { call_functionality1(args); call_functionality2(args); call_functionality3(args); } Whereas in the functional approach, we could have the set of 3 functionalities as a collection: fusion::vector<...> functionSet = {functionality1(), functionality2(), functionality3}; And then have a single "driver" function that goes through this colleciton: smallFunction(args) { fusion::for_each(functionSet, invoke_(args) );//This will invoke all three functionalities //Also note that the resulting object code will be exactly as in #1 } This way there will be more reflection on the structure of the application. It's easier to change the behavior of this kind of software. Note, that runtime vs. static is irrelevant for this discussion. It could be either std::vector<>, fusion::vector<> or mpl::vector<>, depending on the design. If you take it even further, then the core "driver" function will take the function to invoke on every element of the list as an argument: driverFunction(Func) { fusion::for_each(functionSet, Func); } If we just want all of the functionalities to be executed sequentially, we'd call driverFunction(invoke_(args)); We could also execute them conditionally (i.e. only if every predecessor succedes): driverFunction(conditional_invoke_(args)); And here how you'd finally use the trap_exceptions: driverFunction(compose(invoke_(args), trap_exceptions(Ex1, Ex2, Ex3))); This will go over the list of function and execute them unconditionally while catching and handling exceptions Ex1, Ex2 and Ex3. Thanks, Andy.

At Fri, 30 Jul 2010 16:21:13 -0400, Andy Venikov wrote:
I recently took a crack at this task and came up with a generic way to trap exception.
Here's a brief API:
trap_exceptions(<list-of-exception-handlers>);
I think there's something like this in Boost.Python, and, IIUC, in Boost.Test as well. Ah, yes. I suggest you review this thread: http://www.mail-archive.com/boost@lists.boost.org/msg05179.html -- Dave Abrahams BoostPro Computing http://www.boostpro.com

David Abrahams wrote:
At Fri, 30 Jul 2010 16:21:13 -0400, Andy Venikov wrote:
I recently took a crack at this task and came up with a generic way to trap exception.
Here's a brief API:
trap_exceptions(<list-of-exception-handlers>);
I think there's something like this in Boost.Python, and, IIUC, in Boost.Test as well. Ah, yes. I suggest you review this thread: http://www.mail-archive.com/boost@lists.boost.org/msg05179.html
Thanks for the link, it shows that there's an interest in something like that. If I understand that thread correctly, boost.Python provides a way to dynamically register exception translators (which for all intents and purposes are just handlers)for different types of exceptions. While I think that it's somewhat similar to trap_exceptions, the purposes are very different. Boost.Python.exception_transator is totally dynamic, hence the need to use virtual functions and "new". You also have to use the "register" mechanism to register all your handlers individually. I understand that this approach is a must where you may have dynamic components like in Boost.Python and Boos.Test, but when you are simply trying to re-factor your code so that the resulting binary is the same, but the source is organized better, I think a static approach is more fitting. If I didn't have a requirement to support function pointers, then the result of trap_exceptions could've been just a type: trap_exceptions<...list of exception handling functor types...>::type Even with function pointers, their type and their number is known and queriable (they are stored in a tuple<>). The only indirection here is the function call itself. When functors are used, there's no indirection whatsoever. Thanks, Andy.
participants (3)
-
Andy Venikov
-
David Abrahams
-
Stewart, Robert