
Beman, From experience with using asio -- Can we (please?) have ignored() just like we have throws() that builds empty error_code? In quite a lot of places I don't want to deal with exceptions (since I don't know what thread it will come out to) and I don't care for the error too much (like retrieving address of possible disconnected endpoint). So, I have half of the code that looks like this socket.close(boost::system::error_code()); or, even more unreadable boost::asio::some_call(boost::system::error_code()).some_another_call(boost::system::error_code()); . Since then I introduced "ignored" macro (#define ignored boost::system::error_code()), code looks a bit more understandable, so I thought it might be a good idea to have it in a fact0ry-function form just like we will have throws()? Thank you very much, Andrey On Thu, 22 Oct 2009 07:27:22 -0600, Beman Dawes <bdawes@acm.org> wrote:
Christopher Kohlhoff wrote:
I will reluctantly drag my brain out of holiday mode to put in my 2c here :)
Sorry to drag your brain in unwelcome directions, but thanks for taking the time to reply:-)
On Tue, 20 Oct 2009 11:15 -0400, "Beman Dawes" <bdawes@acm.org> wrote:
A POSIX sub-group is looking at C++ bindings for POSIX.
They are talking about a filesystem library binding at least loosely based on Boost.System, Boost.Filesystem, and C++ TR2.
In looking at http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2838.html, "Library Support for Hybrid Error Handling (Rev 2)", they are concerned about specifying hybrid error handling like this:
void f(error_code& ec=throws());
They would rather see it specified like this:
void f(error_code* ec=0); [...]
Although the reference form is usual for C++, the use of anything other than the default is limited, and usually in low-level code at that. As background to my point of view, I would say that this experience is not true for Asio users. The use of the non-defaulted call (or rather, its equivalent in Asio) is not limited, but widespread.
Point taken.
Where applicable, Asio provides overloads: void f(); error_code f(error_code& ec); The throwing overloads are most typically in small programs or at program startup. Non-trivial programs will tend towards the second, non-throwing overload. It is the nature of network applications to react to errors as part of normal control flow, and exception handling is not a good approach for that.
Regardless, I'd like to hear Boosters views on the two interfaces above. Which do you prefer? Why? First, allow me to register my strong dislike for the pointer form. In fact, I am not in favour of any specification that disallows the existence of two separate overloads: throwing and non-throwing.
The point of the paper was to allow implementors to provide two separate overloads if they prefer. I'm doing so for some of the Boost.Filesystem operational functions.
By the way, I agree with Thomas Witt's assertion that the second form return void so that the function can later be changed to return some useful value without disruption to existing code, and for consistency with functions that already return a non-void result.
This: void f(error_code* ec = 0); supports both throwing and non-throwing modes. From the compiler's point of view, any calling code must also support exceptions and so a space and/or time cost is paid for unwinding, even if it is not required. A two-overload approach lets a library do something like: void f(); void f(error_code& ec) [[nothrow]];
Right. That's what the paper was supposed to allow, or at least a version without the [[nothrow]]. I guess that didn't come through clearly enough.
Aside 1: I apologize; I wasn't following [[nothrow]] through the committee, and hadn't even considered that it might impact error handling.
Aside 2: Isn't the syntax "void f [[nothrow]] (error_code& ec);" rather than "void f(error_code& ec) [[nothrow]];"?
Regardless, [[nothrow]] may mean that users expect and demand the two overload specification, even where it will have no measurable impact on performance.
Even if [[nothrow]] doesn't make it into C++0x, I think the cat is out of the bag. User expectations and demands will just morph to:
void f(); void f(error_code& ec); // [[nothrow]]
Thus I'm going to seriously consider moving all of Boost.Filesystem to the two overload form, even though it nearly doubles the number of signatures.
In user code that calls the second form pays no cost for exception handling, and the compiler may do more static analysis and optimisation based on the nothrow attribute. It seems a bit odd to be quibbling over the cost of comparing pointers when some costs of exception handling are forced on everybody. Second, I do not pay the "explosion" of overloads argument. (Yes, there is a doubling.) The potential "explosion" comes from a throwing operation with defaulted arguments: void f(int a, int b = 0, int c = 0); where ideally the user would like to see the following non-throwing variants: void f(int a, error_code& ec); void f(int a, int b, error_code& ec); void f(int a, int b, int c, error_code& ec); However the proposed pointer form would be void f(int a, int b = 0, int c = 0, error_code* ec = 0); And so a user must specify all arguments to use the non-throwing form. If you're happy to require users to do so, then the only non-throwing reference-based overload required is: void f(int a, int b, int c, error_code& ec); I have had some Asio users complain that I have left out some non-throwing overloads. They now have to be aware of the default arguments where otherwise they could have ignored them. Therefore, the "explosion" is a cost that can be paid by a library to improve usability, but it is not inherent in the overload approach. However, it's true there is a doubling of overloads. In my opinion it is a cost worth paying (in specification and implementation) to allow users to avoid the costs of exception handling. In my experience, it's not *that* big a cost anyway. So as I said, I'm not in favour of a specification that excludes that approach. If there was to be a change to the throwing/non-throwing error_code idiom, then what I would suggest is putting the error_code parameter *first*. For example: void f(int a, int b = 0, int c = 0); void f(error_code& ec, int a, int b = 0, int c = 0); This avoids the explosion issue due to defaulted arguments, although you still get doubling. What's especially nice is that it should work with C++0x variadic templates, while the other approaches do not AFAIK.
Interesting. I had briefly considered making error_code& ec the first argument, but rejected it as an unusual ordering of arguments that would draw criticism. But I hadn't considered the variadic template issue, which seems to be coming up in other designs, too.
I'll give that some more thought. It really makes sense in the presence of other arguments with defaults, and for consistency perhaps even when a function has no defaulted arguments.
Thanks for your comments! Although you just used [[nothrow]] in passing, it was a real eye-opener for me.
--Beman