Boost logo

Boost :

Subject: Re: [boost] [system][filesystem v3] Question about error_code arguments
From: Andrey Tcherepanov (moyt63c02_at_[hidden])
Date: 2009-10-22 10:39:40


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_at_[hidden]> 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_at_[hidden]> 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


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk