Boost logo

Boost :

Subject: Re: [boost] [system][filesystem v3] Question about error_code arguments
From: Christopher Kohlhoff (chris_at_[hidden])
Date: 2009-10-20 19:48:47


I will reluctantly drag my brain out of holiday mode to put in my 2c
here :)

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.

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.

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]];

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.

Cheers,
Chris


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