Boost logo

Boost :

Subject: Re: [boost] [system][filesystem v3] Question about error_code arguments
From: Beman Dawes (bdawes_at_[hidden])
Date: 2009-10-22 09:27:22


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