Discussion:
[boost] expected/result/etc
Michael Marcin
2016-01-26 06:16:54 UTC
Permalink
Some months ago there was a lot of discussion regarding Rust style
return values for non-exceptional error handling.

Did anything ever come of this?
Is there a useable library?

Apologies if I missed an announcement (I've been away for a bit).


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas
2016-01-26 10:03:46 UTC
Permalink
On 26 Jan 2016 at 0:16, Michael Marcin wrote:

> Some months ago there was a lot of discussion regarding Rust style
> return values for non-exceptional error handling.

Mine was one of many discussed. Its current name is Outcome.

> Did anything ever come of this?

Yes, Outcome continues to mature as it is being used to write AFIO
v2. The non-futurey bits are quite mature by now and have seen very
extensive usage. The futurey bits aren't being used in the AFIO v2
rewrite yet, and so haven't had the same quantity of very fine level
tweaking.

If I do say so myself, it is *spectacularly* well suited to this use
case, producing seriously high quality assembler output. I am very,
very pleased with it.

> Is there a useable library?

https://github.com/ned14/boost.outcome

It requires VS2015 Update 1 or newer, or any C++ 14 compiler. Some
example AFIO v2 code using it:

https://github.com/BoostGSoC13/boost.afio/blob/master/fs_probe/include
/handle.hpp

https://github.com/BoostGSoC13/boost.afio/blob/master/fs_probe/include
/detail/impl/windows/handle.ipp

As you'll see, the enormous win is that almost every function apart
from constructors is now noexcept. That causes the compiler to do a
very good job indeed of inlining and eliminating code from the
assembler output, plus static analysis tools spot when you forget to
properly handle an errored return because it'll try throwing in a
noexcept function. I expect AFIO v2 to have an undetectable overhead
relative to syscalls, even no op syscalls.

Two big items remain todo for Outcome:

1. I want to write a clang-tidy extension which rewrites any outcome
returning function to be noexcept unless commented otherwise. This
prevents one forgetting to add noexcept.

2. I want another clang-tidy extension to mark up outcome returning
functions with the compiler-specific markup to complain if you forget
to do something with the returned value. Right now outcomes have the
same problem as C error returns, one can forget to handle them
altogether.

I also strongly emphasise that nothing in Outcome is fixed, things
may yet change. Outcome is not in the Boost review queue, nor may
never be.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/
http://ie.linkedin.com/in/nialldouglas/
Michael Marcin
2016-01-27 02:19:36 UTC
Permalink
On 1/26/2016 4:03 AM, Niall Douglas wrote:
> On 26 Jan 2016 at 0:16, Michael Marcin wrote:
>
>> Some months ago there was a lot of discussion regarding Rust style
>> return values for non-exceptional error handling.
>
> Mine was one of many discussed. Its current name is Outcome.

I like the name.

> If I do say so myself, it is *spectacularly* well suited to this use
> case, producing seriously high quality assembler output. I am very,
> very pleased with it.

Thanks, that is very important to me.

>> Is there a useable library?
>
> https://github.com/ned14/boost.outcome
>
> It requires VS2015 Update 1 or newer, or any C++ 14 compiler.

Again thanks.
I'm using vs2015 update 1 so that works for me.
I'll give it a try.

>
> Two big items remain todo for Outcome:
>
> 1. I want to write a clang-tidy extension which rewrites any outcome
> returning function to be noexcept unless commented otherwise. This
> prevents one forgetting to add noexcept.
>
> 2. I want another clang-tidy extension to mark up outcome returning
> functions with the compiler-specific markup to complain if you forget
> to do something with the returned value. Right now outcomes have the
> same problem as C error returns, one can forget to handle them
> altogether.

Neither of these sound important to me, but to each his own.

> I also strongly emphasise that nothing in Outcome is fixed, things
> may yet change. Outcome is not in the Boost review queue, nor may
> never be.

Fair enough.
Would you like feedback on it if I use it anyways?

Thanks.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas
2016-01-27 09:11:26 UTC
Permalink
On 26 Jan 2016 at 20:19, Michael Marcin wrote:

> > I also strongly emphasise that nothing in Outcome is fixed, things
> > may yet change. Outcome is not in the Boost review queue, nor may
> > never be.
>
> Fair enough.

To be more specific, I am still not happy with the boilerplate macros
BOOST_OUTCOME_PROPAGATE_*, BOOST_OUTCOME_THROW_* and so on which save
typing out code which converts errors/exceptions from one type of
outcome/result/option into another, and these may yet change
significantly.

There is still a bit of sloppiness in the API design of
outcome/result/option, and you'll probably notice that yourself. The
sloppiness is partially keeping options option for further reduction
of boilerplate typing, partially cruft, partially laziness.

A version of this actually presented for review here would remove
rather than add, a pared down to essentials edition would be valuable
once I completely understand what the essentials exactly are. That
said, I'm not expecting to present Outcome for entry to Boost as a
library in itself: Outcome will be an internal library for AFIO v2,
which in turn will be an internal library for my transactional
embedded key value database.

> Would you like feedback on it if I use it anyways?

Oh yes please, the futurey stuff has some definite showstopper bugs
but the remainder I currently believe to be ready for feedback from
others.

BTW I am aware the unit tests are currently failing, and I'll try to
push a fix (it's trivial) shortly. The CIs for Outcome has actually
stopped working in November, I hadn't noticed till now.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/
http://ie.linkedin.com/in/nialldouglas/
Domagoj Saric
2016-01-27 21:44:19 UTC
Permalink
On 27.1.2016. 10:11, Niall Douglas wrote:
> A version of this actually presented for review here would remove
> rather than add, a pared down to essentials edition would be valuable
> once I completely understand what the essentials exactly are.

IMO that'd be useful: if you could separate/extract the
'std::expected-like' part of Outcome (from the
future/promise/concurrency whatnots) we could then perhaps join efforts
- after all we both seem to be after a modern err-handling mechanism
that looks nice, adapts well and compiles mighty tight ;D


--
"What Huxley teaches is that in the age of advanced technology,
spiritual devastation is more likely to come from an enemy with a
smiling face than from one whose countenance exudes suspicion and hate."
Neil Postman



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Michael Marcin
2016-02-03 04:25:16 UTC
Permalink
On 1/27/2016 3:11 AM, Niall Douglas wrote:
>
>> Would you like feedback on it if I use it anyways?
>
> Oh yes please, the futurey stuff has some definite showstopper bugs
> but the remainder I currently believe to be ready for feedback from
> others.

FYI it generates some noise when using:
#include <boost/outcome/monad.hpp>
#include <boost/log/trivial.hpp>

2>C:\code\boost\boost_1_60_0\boost/move/core.hpp(341): warning C4005:
'BOOST_RV_REF': macro redefinition
2>
c:\code\boost\boost_1_60_0\boost\outcome\v1\../bindlib/include/boost/config.hpp(197):
note: see previous definition of 'BOOST_RV_REF'
2>C:\code\boost\boost_1_60_0\boost/move/core.hpp(379): warning C4005:
'BOOST_COPY_ASSIGN_REF': macro redefinition
2>
c:\code\boost\boost_1_60_0\boost\outcome\v1\../bindlib/include/boost/config.hpp(201):
note: see previous definition of 'BOOST_COPY_ASSIGN_REF'
2>C:\code\boost\boost_1_60_0\boost/move/core.hpp(385): warning C4005:
'BOOST_FWD_REF': macro redefinition
2>
c:\code\boost\boost_1_60_0\boost\outcome\v1\../bindlib/include/boost/config.hpp(193):
note: see previous definition of 'BOOST_FWD_REF'


You might've already noticed I guess because reversing the includes
makes it shut up.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas
2016-02-03 09:10:09 UTC
Permalink
On 2 Feb 2016 at 22:25, Michael Marcin wrote:

> >> Would you like feedback on it if I use it anyways?
> >
> > Oh yes please, the futurey stuff has some definite showstopper bugs
> > but the remainder I currently believe to be ready for feedback from
> > others.
>
> FYI it generates some noise when using:
> #include <boost/outcome/monad.hpp>
> #include <boost/log/trivial.hpp>
>
> 2>C:\code\boost\boost_1_60_0\boost/move/core.hpp(341): warning C4005:
> 'BOOST_RV_REF': macro redefinition
> 2>
> c:\code\boost\boost_1_60_0\boost\outcome\v1\../bindlib/include/boost/config.hpp(197):
> note: see previous definition of 'BOOST_RV_REF'
> 2>C:\code\boost\boost_1_60_0\boost/move/core.hpp(379): warning C4005:
> 'BOOST_COPY_ASSIGN_REF': macro redefinition
> 2>
> c:\code\boost\boost_1_60_0\boost\outcome\v1\../bindlib/include/boost/config.hpp(201):
> note: see previous definition of 'BOOST_COPY_ASSIGN_REF'
> 2>C:\code\boost\boost_1_60_0\boost/move/core.hpp(385): warning C4005:
> 'BOOST_FWD_REF': macro redefinition
> 2>
> c:\code\boost\boost_1_60_0\boost\outcome\v1\../bindlib/include/boost/config.hpp(193):
> note: see previous definition of 'BOOST_FWD_REF'
>
>
> You might've already noticed I guess because reversing the includes
> makes it shut up.

You're using Outcome in standalone non-Boost configuration in
combination with Boost, so the "Boost-lite" emulation it configures
will collide with Boost.

As you noticed if you include Outcome after Boost it spots you've
already included Boost and turns standalone off.

You can configure Outcome to use Boost natively from the get go via
macros. See its config.hpp. Or just keep including Outcome after all
other Boost headers.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/
http://ie.linkedin.com/in/nialldouglas/
Michael Marcin
2016-02-04 01:11:09 UTC
Permalink
On 2/3/2016 3:10 AM, Niall Douglas wrote:
>
> You're using Outcome in standalone non-Boost configuration in
> combination with Boost, so the "Boost-lite" emulation it configures
> will collide with Boost.
>
> As you noticed if you include Outcome after Boost it spots you've
> already included Boost and turns standalone off.
>
> You can configure Outcome to use Boost natively from the get go via
> macros. See its config.hpp. Or just keep including Outcome after all
> other Boost headers.
>

Ah I see, clever, thanks.



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Robert Ramey
2016-02-04 01:18:02 UTC
Permalink
On 2/3/16 5:11 PM, Michael Marcin wrote:
> On 2/3/2016 3:10 AM, Niall Douglas wrote:
>>
>> You're using Outcome in standalone non-Boost configuration in
>> combination with Boost, so the "Boost-lite" emulation it configures
>> will collide with Boost.
>>
>> As you noticed if you include Outcome after Boost it spots you've
>> already included Boost and turns standalone off.
>>
>> You can configure Outcome to use Boost natively from the get go via
>> macros. See its config.hpp. Or just keep including Outcome after all
>> other Boost headers.

I think it's very unwise to make behavior of some library dependent upon
header order. It's fragile and non-intuitive and leads to bugs or
unexpected behavior which can be very, very, very hard to find.

>>
>
> Ah I see, clever, thanks.

No doubt - but perhaps too much so.

Robert Ramey


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas
2016-02-04 08:16:15 UTC
Permalink
On 3 Feb 2016 at 17:18, Robert Ramey wrote:

> >> You're using Outcome in standalone non-Boost configuration in
> >> combination with Boost, so the "Boost-lite" emulation it configures
> >> will collide with Boost.
> >>
> >> As you noticed if you include Outcome after Boost it spots you've
> >> already included Boost and turns standalone off.
> >>
> >> You can configure Outcome to use Boost natively from the get go via
> >> macros. See its config.hpp. Or just keep including Outcome after all
> >> other Boost headers.
>
> I think it's very unwise to make behavior of some library dependent upon
> header order. It's fragile and non-intuitive and leads to bugs or
> unexpected behavior which can be very, very, very hard to find.

Ordinarily yes. In this case however, the Boost-lite macros have the
same effect as the Boost ones, so redefining them is mostly safe,
albeit with annoying warnings.

For the record, it is Boost which is at fault here, not my code.
Boost should not be unilaterally setting BOOST_* macros without
checking if they are already defined.

> > Ah I see, clever, thanks.
>
> No doubt - but perhaps too much so.

Maybe for some. However my Boost abstraction layer has proven to be
an enormous boon for my development productivity - no more messing
around with config, build or installing dependencies. I just get to
work knowing the resulting libraries will take advantage of Boost
where available/needed, and if not fall back on the C++ 14/1z STL
with Boost-lite emulation. I recently started porting AFIO v2 to
POSIX, and I was quite surprised the very first time I pressed
compile it actually "just worked" picking up Boost.Filesystem
automagically on that platform (the clang now bundled with VS2015
Update 1 also helped a lot of course to ensure the code written on
Windows would compile without error on FreeBSD's clang).

I'm very pleased with the layer, anyway, it is worth every day all
those months I poured into developing it.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/
http://ie.linkedin.com/in/nialldouglas/
Sam Kellett
2016-02-04 13:06:59 UTC
Permalink
On 4 February 2016 at 08:16, Niall Douglas <***@nedprod.com>
wrote:

> On 3 Feb 2016 at 17:18, Robert Ramey wrote:
>
> > >> You're using Outcome in standalone non-Boost configuration in
> > >> combination with Boost, so the "Boost-lite" emulation it configures
> > >> will collide with Boost.
> > >>
> > >> As you noticed if you include Outcome after Boost it spots you've
> > >> already included Boost and turns standalone off.
> > >>
> > >> You can configure Outcome to use Boost natively from the get go via
> > >> macros. See its config.hpp. Or just keep including Outcome after all
> > >> other Boost headers.
> >
> > I think it's very unwise to make behavior of some library dependent upon
> > header order. It's fragile and non-intuitive and leads to bugs or
> > unexpected behavior which can be very, very, very hard to find.
>
> Ordinarily yes. In this case however, the Boost-lite macros have the
> same effect as the Boost ones, so redefining them is mostly safe,
> albeit with annoying warnings.
>

that's obviously not true seeing as somebody hit this problem seemingly
almost immediately

For the record, it is Boost which is at fault here, not my code.
> Boost should not be unilaterally setting BOOST_* macros without
> checking if they are already defined.
>

perhaps non-boost libraries shouldn't be using the boost:: and BOOST_
namespaces?

this may be OT but i find it strange that so many potential libraries are
allowed to brand themselves as boost with just a single "this is not an
official boost library" in their readme.

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Michael Marcin
2016-02-04 13:45:11 UTC
Permalink
On 2/4/2016 7:06 AM, Sam Kellett wrote:
>>
>> Ordinarily yes. In this case however, the Boost-lite macros have the
>> same effect as the Boost ones, so redefining them is mostly safe,
>> albeit with annoying warnings.
>>
>
> that's obviously not true seeing as somebody hit this problem seemingly
> almost immediately
>

Eh? That's exactly what happened. Annoying warnings with no other issues.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Sam Kellett
2016-02-04 14:45:32 UTC
Permalink
On 4 February 2016 at 13:45, Michael Marcin <***@gmail.com> wrote:

> On 2/4/2016 7:06 AM, Sam Kellett wrote:
>
>>
>>> Ordinarily yes. In this case however, the Boost-lite macros have the
>>> same effect as the Boost ones, so redefining them is mostly safe,
>>> albeit with annoying warnings.
>>>
>>>
>> that's obviously not true seeing as somebody hit this problem seemingly
>> almost immediately
>>
>>
> Eh? That's exactly what happened. Annoying warnings with no other issues.


sorry i don't think that came off how i meant it... what i mean is this is
kinda asking for trouble. what happens if the boost macro changes?

wouldn't something like this be better:

#ifdef BOOST_XXX
#define MY_XXX BOOST_XXX
#else
#define MY_XXX /* boost_lite thing here */
#endif

redefining a macro in somebody else's 'namespace' is akin to opening up the
std namespace to redefine vector.

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas
2016-02-04 15:22:40 UTC
Permalink
On 4 Feb 2016 at 14:45, Sam Kellett wrote:

> >>> Ordinarily yes. In this case however, the Boost-lite macros have the
> >>> same effect as the Boost ones, so redefining them is mostly safe,
> >>> albeit with annoying warnings.
> >>>
> >>>
> >> that's obviously not true seeing as somebody hit this problem seemingly
> >> almost immediately
> >>
> >>
> > Eh? That's exactly what happened. Annoying warnings with no other issues.
>
>
> sorry i don't think that came off how i meant it... what i mean is this is
> kinda asking for trouble. what happens if the boost macro changes?
>
> wouldn't something like this be better:
>
> #ifdef BOOST_XXX
> #define MY_XXX BOOST_XXX
> #else
> #define MY_XXX /* boost_lite thing here */
> #endif
>
> redefining a macro in somebody else's 'namespace' is akin to opening up the
> std namespace to redefine vector.

This is exactly what my Boost-lite emulation already does.

The problem is when you include my stuff first and then include Boost
without telling my stuff you actually want to use Boost, then Boost's
definitions collide with my emulated ones.

If Boost did as you suggested there would be no warnings, and I'm
defining the same functionally speaking thing as Boost is so there
should be no breakage (insert many caveats omitted for brevity here).

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/
http://ie.linkedin.com/in/nialldouglas/
Sam Kellett
2016-02-04 16:05:21 UTC
Permalink
On 4 February 2016 at 15:22, Niall Douglas <***@nedprod.com>
wrote:

> On 4 Feb 2016 at 14:45, Sam Kellett wrote:
>
> > >>> Ordinarily yes. In this case however, the Boost-lite macros have the
> > >>> same effect as the Boost ones, so redefining them is mostly safe,
> > >>> albeit with annoying warnings.
> > >>>
> > >>>
> > >> that's obviously not true seeing as somebody hit this problem
> seemingly
> > >> almost immediately
> > >>
> > >>
> > > Eh? That's exactly what happened. Annoying warnings with no other
> issues.
> >
> >
> > sorry i don't think that came off how i meant it... what i mean is this
> is
> > kinda asking for trouble. what happens if the boost macro changes?
> >
> > wouldn't something like this be better:
> >
> > #ifdef BOOST_XXX
> > #define MY_XXX BOOST_XXX
> > #else
> > #define MY_XXX /* boost_lite thing here */
> > #endif
> >
> > redefining a macro in somebody else's 'namespace' is akin to opening up
> the
> > std namespace to redefine vector.
>
> This is exactly what my Boost-lite emulation already does.
>

it can't be exactly what your boost-lite emulation does else you wouldn't
get redefinition warnings. i'm not redefining anybody else's macros...

>
> The problem is when you include my stuff first and then include Boost
> without telling my stuff you actually want to use Boost, then Boost's
> definitions collide with my emulated ones.
>

this problem doesn't exist at all if you stick to defining macros with your
own prefix...

>
> If Boost did as you suggested there would be no warnings, and I'm
> defining the same functionally speaking thing as Boost is so there
> should be no breakage (insert many caveats omitted for brevity here).
>

so boost should assume that at any time somebody else may change or replace
their code? how is that practical / possible to test?


On 4 February 2016 at 15:22, Niall Douglas <***@nedprod.com>
wrote:

> On 4 Feb 2016 at 14:45, Sam Kellett wrote:
>
> > >>> Ordinarily yes. In this case however, the Boost-lite macros have the
> > >>> same effect as the Boost ones, so redefining them is mostly safe,
> > >>> albeit with annoying warnings.
> > >>>
> > >>>
> > >> that's obviously not true seeing as somebody hit this problem
> seemingly
> > >> almost immediately
> > >>
> > >>
> > > Eh? That's exactly what happened. Annoying warnings with no other
> issues.
> >
> >
> > sorry i don't think that came off how i meant it... what i mean is this
> is
> > kinda asking for trouble. what happens if the boost macro changes?
> >
> > wouldn't something like this be better:
> >
> > #ifdef BOOST_XXX
> > #define MY_XXX BOOST_XXX
> > #else
> > #define MY_XXX /* boost_lite thing here */
> > #endif
> >
> > redefining a macro in somebody else's 'namespace' is akin to opening up
> the
> > std namespace to redefine vector.
>
> This is exactly what my Boost-lite emulation already does.
>
> The problem is when you include my stuff first and then include Boost
> without telling my stuff you actually want to use Boost, then Boost's
> definitions collide with my emulated ones.
>
> If Boost did as you suggested there would be no warnings, and I'm
> defining the same functionally speaking thing as Boost is so there
> should be no breakage (insert many caveats omitted for brevity here).
>
> Niall
>
> --
> ned Productions Limited Consulting
> http://www.nedproductions.biz/
> http://ie.linkedin.com/in/nialldouglas/
>
>
>
>
> _______________________________________________
> Unsubscribe & other changes:
> http://lists.boost.org/mailman/listinfo.cgi/boost
>

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Gavin Lambert
2016-02-04 22:34:52 UTC
Permalink
On 5/02/2016 04:22, Niall Douglas wrote:
> On 4 Feb 2016 at 14:45, Sam Kellett wrote:
>> redefining a macro in somebody else's 'namespace' is akin to opening up the
>> std namespace to redefine vector.
>
> This is exactly what my Boost-lite emulation already does.
>
> The problem is when you include my stuff first and then include Boost
> without telling my stuff you actually want to use Boost, then Boost's
> definitions collide with my emulated ones.
>
> If Boost did as you suggested there would be no warnings, and I'm
> defining the same functionally speaking thing as Boost is so there
> should be no breakage (insert many caveats omitted for brevity here).

I think what Sam was trying to get at is that instead of declaring
things in the boost namespace, your abstraction layer should declare
things in some unique namespace (mostly as typedefs and usings from
either std:: or boost:: as appropriate), and then your code that depends
on this should exclusively use your abstraction layer namespace, not the
boost namespace.

This will avoid generating any conflicts or warnings even if Boost and
your emulation are included at the same time. Though the potential
downside is that in cases where you want it to use the Boost version, it
might not due to the include order, and you could end up with some
components using Boost and others not (although that should generate
link errors).


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas
2016-02-04 23:00:29 UTC
Permalink
On 5 Feb 2016 at 11:34, Gavin Lambert wrote:

> I think what Sam was trying to get at is that instead of declaring
> things in the boost namespace, your abstraction layer should declare
> things in some unique namespace (mostly as typedefs and usings from
> either std:: or boost:: as appropriate), and then your code that depends
> on this should exclusively use your abstraction layer namespace, not the
> boost namespace.
>
> This will avoid generating any conflicts or warnings even if Boost and
> your emulation are included at the same time. Though the potential
> downside is that in cases where you want it to use the Boost version, it
> might not due to the include order, and you could end up with some
> components using Boost and others not (although that should generate
> link errors).

Regarding the macro namespace collision, it's a fair point, but as I
have said more than once now the semantic meaning of the macros is
the same. So BOOST_RV_REF or whatever means and does the exact same
thing, and the warnings are only occurring because the library is
being configured incorrectly anyway. If you configured it right, no
warnings.

There is also no risk at all of link problems because of the
namespace binds. You may or may not remember Gavin that BindLib does
explicit hard binding of a specific version of a dependent library
into the destination namespace, so when Outcome is configured to use
an emulation of Boost.X, it *always* uses that with no effect nor
consequence on any other use of the real Boost.X in the same
translation unit.

AFIO v1 has a unit test where an AFIO built using emulated Boost is
compiled alongside an AFIO built using real Boost in the same
translation unit. It all compiles and links and passes all unit tests
perfectly.

This stuff is very safe and reliable and coexists happily with all
other code. All the normal assumptions about stuff colliding even in
the same translation unit does not apply for any of the BindLib based
libraries. If the C preprocessor namespace were as configurable as
the C++ namespace, I could prevent the above (probably harmless)
warnings, but the language support just isn't there so we are at
where we are at.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/
http://ie.linkedin.com/in/nialldouglas/
Sam Kellett
2016-02-05 08:36:26 UTC
Permalink
On 4 February 2016 at 23:00, Niall Douglas <***@nedprod.com>
wrote:

> On 5 Feb 2016 at 11:34, Gavin Lambert wrote:
>
> > I think what Sam was trying to get at is that instead of declaring
> > things in the boost namespace, your abstraction layer should declare
> > things in some unique namespace (mostly as typedefs and usings from
> > either std:: or boost:: as appropriate), and then your code that depends
> > on this should exclusively use your abstraction layer namespace, not the
> > boost namespace.
> >
> > This will avoid generating any conflicts or warnings even if Boost and
> > your emulation are included at the same time. Though the potential
> > downside is that in cases where you want it to use the Boost version, it
> > might not due to the include order, and you could end up with some
> > components using Boost and others not (although that should generate
> > link errors).
>
> Regarding the macro namespace collision, it's a fair point, but as I
> have said more than once now the semantic meaning of the macros is
> the same. So BOOST_RV_REF or whatever means and does the exact same
> thing, and the warnings are only occurring because the library is
> being configured incorrectly anyway. If you configured it right, no
> warnings
>

what's the disadvantage of using your own namespace? afaics it is exactly
the same only without the possibility of a namespace collision.

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Michael Marcin
2016-02-04 13:44:05 UTC
Permalink
On 2/3/2016 7:18 PM, Robert Ramey wrote:
> On 2/3/16 5:11 PM, Michael Marcin wrote:
>> On 2/3/2016 3:10 AM, Niall Douglas wrote:
>>>
>>> You're using Outcome in standalone non-Boost configuration in
>>> combination with Boost, so the "Boost-lite" emulation it configures
>>> will collide with Boost.
>>>
>>> As you noticed if you include Outcome after Boost it spots you've
>>> already included Boost and turns standalone off.
>>>
>>> You can configure Outcome to use Boost natively from the get go via
>>> macros. See its config.hpp. Or just keep including Outcome after all
>>> other Boost headers.
>
> I think it's very unwise to make behavior of some library dependent upon
> header order. It's fragile and non-intuitive and leads to bugs or
> unexpected behavior which can be very, very, very hard to find.
>

Sure, but it seems that I just have the library misconfigured is all.

The library doesn't seem to depend on header order unless used in a way
that differs from how it is configured. Which I think is perfectly
reasonable (all bets are off if you lie to it and configure it to think
you don't have boost then turn around and use boost with it IMO).

My clever remark was referring to the boost-lite not to the header ordering.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Domagoj Saric
2016-01-27 21:37:21 UTC
Permalink
On 27.1.2016. 3:19, Michael Marcin wrote:
> On 1/26/2016 4:03 AM, Niall Douglas wrote:
>> On 26 Jan 2016 at 0:16, Michael Marcin wrote:
>>
>>> Some months ago there was a lot of discussion regarding Rust style
>>> return values for non-exceptional error handling.
>>
>> Mine was one of many discussed. Its current name is Outcome.
>
> I like the name.

Me too (nice 'find' Niall;) ...but then again that may be because I'm
'into' the subject so I know what is meant by it. From the POV of a 'new
user' or 'trying not to invent new buzzwords that C++ devs have to
memorize' an overly verbose but more obvious name like result_or_error
or fallible_result that I currently use in Err might be 'better'...so
yes...bikeshedding...:-D


>>> Is there a useable library?
>>
>> https://github.com/ned14/boost.outcome
>>
>> It requires VS2015 Update 1 or newer, or any C++ 14 compiler.
>
> Again thanks.
> I'm using vs2015 update 1 so that works for me.
> I'll give it a try.

In case you missed it, you can also take a look @:
https://github.com/psiha/err
http://boost.2283326.n4.nabble.com/err-RFC-td4681600.html


--
"What Huxley teaches is that in the age of advanced technology,
spiritual devastation is more likely to come from an enemy with a
smiling face than from one whose countenance exudes suspicion and hate."
Neil Postman



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Robert Ramey
2016-01-27 16:32:41 UTC
Permalink
On 1/25/16 10:16 PM, Michael Marcin wrote:
> Some months ago there was a lot of discussion regarding Rust style
> return values for non-exceptional error handling.
>
> Did anything ever come of this?
> Is there a useable library?
>
> Apologies if I missed an announcement (I've been away for a bit).

I confronted and addressed this problem in the Safe Numerics library.

The data type is called "checked_result<R>". Although it was intended to
be internal to the library, I did document and test it independently.
One thing that turned out to be necessary in my application was that it
be constexpr.

I'm not entirely convinced that a general solution is desirable or even
possible. It's a simple data type. But once you start using it you run
into application specific issues such as should it be convertible,
constexpr, etc., ... . On the other hand, if it is worthwhile, to
standardize/libraryize/ etc it's going to entail a huge amount of bike
sheding - as we've already had a taste of.

Robert Ramey



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas
2016-01-27 20:00:26 UTC
Permalink
On 27 Jan 2016 at 8:32, Robert Ramey wrote:

> The data type is called "checked_result<R>". Although it was intended to
> be internal to the library, I did document and test it independently.
> One thing that turned out to be necessary in my application was that it
> be constexpr.

You've reminded me of another area in Outcome which will change:
constexpr for when T is a LiteralType. The changes won't be massive,
it was working some months ago but I had to disable constexpr to get
it working with VS2015 before Update 1. Now Update 1 is out, I ought
to restore constexpr support if that proves sane with C++ 11
constexpr, and have made a note to remind myself.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/
http://ie.linkedin.com/in/nialldouglas/
Domagoj Saric
2016-01-27 21:31:11 UTC
Permalink
On 27.1.2016. 17:32, Robert Ramey wrote:
> I confronted and addressed this problem in the Safe Numerics
> library.
>
> The data type is called "checked_result<R>". Although it was intended
> to be internal to the library, I did document and test it
> independently. One thing that turned out to be necessary in my
> application was that it be constexpr.

Nice, another one ;D


> I'm not entirely convinced that a general solution is desirable or
> even possible. It's a simple data type. But once you start using it
> you run into application specific issues such as should it be
> convertible, constexpr, etc., ... . On the other hand, if it is
> worthwhile, to standardize/libraryize/ etc it's going to entail a
> huge amount of bike sheding - as we've already had a taste of.

Given the amount of independent solutions that keep springing up
"standardization/libraryization" is IMO the hopeful outcome...In my
introduction to Err I start from the already existing
std::error_code&co. to arrive to a new solution that is IMO better in
every respect (std::error_code is after all a C++03-era tool that came a
bit late to the party).
So, if std::error_code was standardized so can some other tool for the
same/similar purpose if it is indeed better in every respect. And the
inevitable bikeshedding can/will hopefully give us a solution that is
atleast adaptable enough so as to be usable even in specialized cases
(e.g. like your's or Niall's) at least through some form of
parametrization (if it is not reusable directly) - so long as we
terminate the duplication and wheel reinvention...


--
"What Huxley teaches is that in the age of advanced technology,
spiritual devastation is more likely to come from an enemy with a
smiling face than from one whose countenance exudes suspicion and hate."
Neil Postman


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Bjorn Reese
2016-01-27 21:57:17 UTC
Permalink
On 01/27/2016 10:31 PM, Domagoj Saric wrote:

> Given the amount of independent solutions that keep springing up
> "standardization/libraryization" is IMO the hopeful outcome...In my

https://github.com/viboes/std-make/blob/master/doc/proposal/expected/DXXXXR0_expected.md


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Michael Marcin
2016-01-28 06:49:38 UTC
Permalink
So looking a few of these libraries/proposals I have a few things I'm
interested in.

- Is it at least as good as a simple hand-rolled solution?
- Can you transport error codes through layers without knowing specifics
of an error?

For the second Niall's outcome/result handles very well I think.
std::error_code and std::exception_ptr transport is good enough for
almost any reasonable type agnostic error transport.

If the direct caller can handle the error returning an error enum
instead of a std::error_code will often be better. It looks like the
DXXXXR0 expected proposal allows for this.

A variant is a good implementation for many types.
And I think clearly the right default behavior.
However, I'm not convinced that a variant is always the best implementation.

Take for example an expected<void*,error_enum>.
Although I haven't done tests I'd be reasonable confident returning this
by value would perform worse than returning
std::pair<void*,error_enum>.

First, the machinery is going to make it store an extra byte at least
for the variant discriminator.

Then, you likely branch before the copy (maybe optimized away by a smart
variant implementation).

In fact in *most* cases of returning a pointer and error_enum you should
be able to get away with returning a the equivalent of
union { void*, error_enum }
As *most* error_enums are relatively small and have values smaller than
any valid heap or stack pointer.
It would be nice to be able to opt in for this.

More generally given
- an error_enum with an OK or 'not an error' value
- a T that is cheap to default construct and cheap to copy/move
Is the library solution better than returning a pair<T, error_enum>?
Or a function returning an error_enum with an out param?



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas
2016-01-28 12:36:33 UTC
Permalink
On 28 Jan 2016 at 0:49, Michael Marcin wrote:

> If the direct caller can handle the error returning an error enum
> instead of a std::error_code will often be better. It looks like the
> DXXXXR0 expected proposal allows for this.

The way I've done it is to define a custom error code category per
category of error throwing thing, thereby naturally extending C++
11/Boost error codes. It may seem like an awful lot of boilerplate
when you just want an enum really, but trust me that it is worth the
effort - you get stuff like free error_code to exception throw to
error_code conversions (you specifically need that machinery to
return error codes from constructors) and all sorts of other goodies
like debug printing. I'd recommend forget about enums for error
coding, extend error_code as it was designed to be extended and
accept the boilerplate which is fire and forget anyway.

I will tell you one thing though: I just don't get the need for
error_code and error_condition to be different in real world usage. I
appreciate the original desire for there to be a type distinction
matching the semantic distinction, but in any code I've written so
far the custom category plus error_code is as much as I need. Outcome
reflects my experience here, and does not support error_condition.

> A variant is a good implementation for many types.
> And I think clearly the right default behavior.
> However, I'm not convinced that a variant is always the best implementation.
>
> Take for example an expected<void*,error_enum>.
> Although I haven't done tests I'd be reasonable confident returning this
> by value would perform worse than returning
> std::pair<void*,error_enum>.
>
> First, the machinery is going to make it store an extra byte at least
> for the variant discriminator.
>
> Then, you likely branch before the copy (maybe optimized away by a smart
> variant implementation).
>
> In fact in *most* cases of returning a pointer and error_enum you should
> be able to get away with returning a the equivalent of
> union { void*, error_enum }
> As *most* error_enums are relatively small and have values smaller than
> any valid heap or stack pointer.
> It would be nice to be able to opt in for this.
>
> More generally given
> - an error_enum with an OK or 'not an error' value
> - a T that is cheap to default construct and cheap to copy/move
> Is the library solution better than returning a pair<T, error_enum>?
> Or a function returning an error_enum with an out param?

My dissatisfaction with the quality of output overhead of other
solutions led me to invest very significant time into this aspect of
Outcome, and there are per-commit unit tests ensuring ideal code
quality output for various canned use cases at
https://github.com/ned14/boost.outcome/tree/master/test/constexprs.

Any hand rolled solution will likely be more surprising unless the
author invests a similar amount of tuning effort. You would be
surprised what upsets compiler optimisers sometimes.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/
http://ie.linkedin.com/in/nialldouglas/
Michael Marcin
2016-01-28 16:32:06 UTC
Permalink
On 1/28/2016 6:36 AM, Niall Douglas wrote:
> On 28 Jan 2016 at 0:49, Michael Marcin wrote:
>
>> If the direct caller can handle the error returning an error enum
>> instead of a std::error_code will often be better. It looks like the
>> DXXXXR0 expected proposal allows for this.
>
> The way I've done it is to define a custom error code category per
> category of error throwing thing, thereby naturally extending C++
> 11/Boost error codes. It may seem like an awful lot of boilerplate
> when you just want an enum really, but trust me that it is worth the
> effort - you get stuff like free error_code to exception throw to
> error_code conversions (you specifically need that machinery to
> return error codes from constructors) and all sorts of other goodies
> like debug printing. I'd recommend forget about enums for error
> coding, extend error_code as it was designed to be extended and
> accept the boilerplate which is fire and forget anyway.
>

I agree and I make this boiler plate for every error enum I have as
well. I find it incredibly useful, in fact I pretty much only throw
std::system_error anymore.

If you are going to handle the error in non-generic code very close to
the caller, I contend there is no need to actually call make_error_code
and wrap that enum into a std::error_code. The enum is typically 4
bytes, a std::error_code is usually 16.

Certainly many times it doesn't matter at all but there are also times
where it would prevent me from having a good faith argument that this is
a strict improvement over a more C-style error handling approach.

>
> My dissatisfaction with the quality of output overhead of other
> solutions led me to invest very significant time into this aspect of
> Outcome, and there are per-commit unit tests ensuring ideal code
> quality output for various canned use cases at
> https://github.com/ned14/boost.outcome/tree/master/test/constexprs.
>

Thanks for the link. I'll take a look.



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas
2016-01-29 13:27:02 UTC
Permalink
On 28 Jan 2016 at 10:32, Michael Marcin wrote:

> If you are going to handle the error in non-generic code very close to
> the caller, I contend there is no need to actually call make_error_code
> and wrap that enum into a std::error_code. The enum is typically 4
> bytes, a std::error_code is usually 16.
>
> Certainly many times it doesn't matter at all but there are also times
> where it would prevent me from having a good faith argument that this is
> a strict improvement over a more C-style error handling approach.

Unlike exception_ptr, error_code has no global memory effects and is
elidable by the optimiser. Assuming there are no calls to extern
functions between the return of an error_code and the handling of it,
you will find your nominal 16 bytes reduced to a direct test of the
state of errno/GetLastError which is of course no overhead at all.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/
http://ie.linkedin.com/in/nialldouglas/
Domagoj Saric
2016-02-07 18:01:17 UTC
Permalink
On 29.1.2016. 14:27, Niall Douglas wrote:
> On 28 Jan 2016 at 10:32, Michael Marcin wrote:
>
>> If you are going to handle the error in non-generic code very close to
>> the caller, I contend there is no need to actually call make_error_code
>> and wrap that enum into a std::error_code. The enum is typically 4
>> bytes, a std::error_code is usually 16.
>>
>> Certainly many times it doesn't matter at all but there are also times
>> where it would prevent me from having a good faith argument that this is
>> a strict improvement over a more C-style error handling approach.
>
> Unlike exception_ptr, error_code has no global memory effects and is
> elidable by the optimiser. Assuming there are no calls to extern
> functions between the return of an error_code and the handling of it,
> you will find your nominal 16 bytes reduced to a direct test of the
> state of errno/GetLastError which is of course no overhead at all.

And only if the function returning the error_code is inlined (or if the
function has internal linkage and your compiler is 'very smart' and sees
that the error_code is unused in all call sites and uses a custom
calling convention that eliminates the error_code)...a good result<T, E>
solution would produce optimal code even on ABI boundaries (e.g. a
function returning a result<iterator_range, errno_t> exported from an
x64 .so would pass the result in registers)...


--
"What Huxley teaches is that in the age of advanced technology,
spiritual devastation is more likely to come from an enemy with a
smiling face than from one whose countenance exudes suspicion and hate."
Neil Postman


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Domagoj Saric
2016-02-04 22:20:14 UTC
Permalink
On 28.1.2016. 13:36, Niall Douglas wrote:
> The way I've done it is to define a custom error code category per
> category of error throwing thing, thereby naturally extending C++
> 11/Boost error codes. It may seem like an awful lot of boilerplate
> when you just want an enum really, but trust me that it is worth the
> effort - you get stuff like free error_code to exception throw to
> error_code conversions (you specifically need that machinery to
> return error codes from constructors)...

You don't need necessarily need to use std::error_code to 'return'
errors from constructors...+ this practice is IMO a bit ugly (i.e.
http://www.gotw.ca/publications/mill13.htm). In C++11 and later I think
this can be solved more cleanly with static noexcept factory functions
(which return outcomes/fallible_results/whatever), a move constructor
and one variadic template constructor which forwards its arguments to
the factory function(s) (so this pair of constructors handles any number
of factory overloads) and forwards the result/return of the factory
function to the move constructor...So, if you want noexcept construction
you use a factory function or a constructor otherwise...


--
"What Huxley teaches is that in the age of advanced technology,
spiritual devastation is more likely to come from an enemy with a
smiling face than from one whose countenance exudes suspicion and hate."
Neil Postman


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Domagoj Saric
2016-01-28 23:26:17 UTC
Permalink
On 28.1.2016. 7:49, Michael Marcin wrote:
> So looking a few of these libraries/proposals I have a few things I'm
> interested in.
>
> - Is it at least as good as a simple hand-rolled solution?

As Niall said, assuming the lib dev paid attention to codegen (i.e.
testing with the disassembly window always open) it should be _better_
then a hand-rolled (i.e. reinvented wheel) solution as today's compilers
are often very smart but sometimes also face-palmingly-dumb and it takes
effort to make'em happy (if possible at all)...


> - Can you transport error codes through layers without knowing specifics
> of an error?
>
> For the second Niall's outcome/result handles very well I think.
> std::error_code and std::exception_ptr transport is good enough for
> almost any reasonable type agnostic error transport.

There are two classical approaches to type agnosticism: (a)
compile-time/templates and (b) runtime/type-erasure (codes,
exception_ptrs, virtual functions/polymorphism). Since you can at any
point go from (a) to (b) but not vice-verse, and since (a) is easier on
the compiler/optimizer (to ensure that you don't pay for what you don't
use), I'd say that the basic solution (the basic, underlying class
template, or two of them as in the case of Err) should go with (a).
Why should code in a particular 'layer'/confined to an ABI and/or API
border, let's call it Lib1, tug around a generic std::error_code (with a
pointer to Lib1ErrorCategory) instead of just using some lib1::error
object which can be (e.g. implicitly) converted to std::error_code or to
std::exception_ptr or to an exception object and throw at the API boundary?

Sure the library can and should help with automating such (a)->(b)
conversions (like std provides functions and traits that can be
specialized for UDTs to automate conversion between error enum values,
error_codes and error_conditions, or like Err provides a function for
transforming error objects into exception objects, etc.) but those are
'implementation details'...


> If the direct caller can handle the error returning an error enum
> instead of a std::error_code will often be better. It looks like the
> DXXXXR0 expected proposal allows for this.
>
> A variant is a good implementation for many types.
> And I think clearly the right default behavior.
> However, I'm not convinced that a variant is always the best
> implementation.

Well AFAICT all the implementations are/have to be based on
discriminated unions/'variants' of some kind...WRT to QoI you just have
to stop thinking about it as just another boost::variant (for example it
is limited to exactly two types which immediately opens up space for
'clever specialisations')...


> Take for example an expected<void*,error_enum>.
> Although I haven't done tests I'd be reasonable confident returning this
> by value would perform worse than returning
> std::pair<void*,error_enum>.
>
> First, the machinery is going to make it store an extra byte at least
> for the variant discriminator.
>
> Then, you likely branch before the copy (maybe optimized away by a smart
> variant implementation).
>
> In fact in *most* cases of returning a pointer and error_enum you should
> be able to get away with returning a the equivalent of
> union { void*, error_enum }
> As *most* error_enums are relatively small and have values smaller than
> any valid heap or stack pointer.
> It would be nice to be able to opt in for this.

As you yourself hint here, most or all of this overhead can be
'optimised' (so as to perform _at least_ as good as hand written
solution while still using the intuitive generic interface) under the
condition that we don't fix/limit it to use predefined 'runtimey' error
abstractions like std::error_code (which are larger than the CPU word
and touch things with vtables which in turn make them harder to
pass/return in registers and cause other bloat like RTTI records)...

For example err::result_or_error has a specialization for 'empty' error
objects (use for APIs that use thread local 'last error' codes, like
Win32 or errno)...

But, again, these are 'implementation details' and I don't think we've
gone far enough in the 'bikeshedding' to deal with that ;)
(IMO the important thing is just the agreement that the chosen solution
must allow for optimal codegen with existing or near-future compilers...)


> More generally given
> - an error_enum with an OK or 'not an error' value
> - a T that is cheap to default construct and cheap to copy/move
> Is the library solution better than returning a pair<T, error_enum>?
> Or a function returning an error_enum with an out param?

Yes it is better (a library solution), because it can be made to produce
'just as good' codegen (or in some cases it is theoretically possible
for some compilers to finally get their act together produce decent
codegen) while providing a reusable solution...


--
"What Huxley teaches is that in the age of advanced technology,
spiritual devastation is more likely to come from an enemy with a
smiling face than from one whose countenance exudes suspicion and hate."
Neil Postman


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Domagoj Saric
2016-01-28 23:28:51 UTC
Permalink
On 27.1.2016. 22:57, Bjorn Reese wrote:
> On 01/27/2016 10:31 PM, Domagoj Saric wrote:
>
>> Given the amount of independent solutions that keep springing up
>> "standardization/libraryization" is IMO the hopeful outcome...In my
>
>
https://github.com/viboes/std-make/blob/master/doc/proposal/expected/DXXXXR0_expected.md

Heh somehow missed this one too...thanks...quite some effort invested in
finding a solution to this 'problem'...now I understand Vincente's
interest in Err :)


--
"What Huxley teaches is that in the age of advanced technology,
spiritual devastation is more likely to come from an enemy with a
smiling face than from one whose countenance exudes suspicion and hate."
Neil Postman


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Emil Dotchevski
2016-01-28 20:02:45 UTC
Permalink
On Wed, Jan 27, 2016 at 10:49 PM, Michael Marcin <***@gmail.com>
wrote:

> So looking a few of these libraries/proposals I have a few things I'm
> interested in.
>
> - Is it at least as good as a simple hand-rolled solution?
> - Can you transport error codes through layers without knowing specifics
> of an error?
>
> For the second Niall's outcome/result handles very well I think.
> std::error_code and std::exception_ptr transport is good enough for almost
> any reasonable type agnostic error transport.
>

Have you considered using Boost Exception? It lets you add arbitrary data
to exception objects, even after they have been thrown, regardless of their
type -- see
http://www.boost.org/doc/libs/release/libs/exception/doc/error_info.html.
The general idea is to adopt a strategy where the exception objects are
augmented with any, even platform-specific relevant data, which then the
catch site can analyze to choose the correct handling.

Cheers,
Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Domagoj Saric
2016-01-28 23:44:19 UTC
Permalink
On 28.1.2016. 21:02, Emil Dotchevski wrote:
> Have you considered using Boost Exception? It lets you add arbitrary data
> to exception objects, even after they have been thrown, regardless of their
> type -- see
> http://www.boost.org/doc/libs/release/libs/exception/doc/error_info.html.
> The general idea is to adopt a strategy where the exception objects are
> augmented with any, even platform-specific relevant data, which then the
> catch site can analyze to choose the correct handling.

Hopefully these efforts will bring us to a solution that will do away
with the, IMO, false dichotomy between "errors" and "exceptions"
(<system_error> vs <exception>) - rather we will start thinking only
about "errors"/error objects which can be transfered in two different
ways - like a ball in a rugby game - it can be passed 'on foot' hand to
hand or it can be thrown over intermediate players to its final
destination (only with errors its in reverse - they are mostly
passed/returned or thrown 'backwards':).
IOW things like error_info will be applicable to 'the' standardized
error type(s) that functions also return, not just throw.


--
"What Huxley teaches is that in the age of advanced technology,
spiritual devastation is more likely to come from an enemy with a
smiling face than from one whose countenance exudes suspicion and hate."
Neil Postman


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Emil Dotchevski
2016-01-29 00:57:16 UTC
Permalink
On Thu, Jan 28, 2016 at 3:44 PM, Domagoj Saric <***@gmail.com> wrote:

> On 28.1.2016. 21:02, Emil Dotchevski wrote:
>
>> Have you considered using Boost Exception? It lets you add arbitrary data
>> to exception objects, even after they have been thrown, regardless of
>> their
>> type -- see
>> http://www.boost.org/doc/libs/release/libs/exception/doc/error_info.html.
>> The general idea is to adopt a strategy where the exception objects are
>> augmented with any, even platform-specific relevant data, which then the
>> catch site can analyze to choose the correct handling.
>>
>
> Hopefully these efforts will bring us to a solution that will do away with
> the, IMO, false dichotomy between "errors" and "exceptions" (<system_error>
> vs <exception>) - rather we will start thinking only about "errors"/error
> objects which can be transfered in two different ways - like a ball in a
> rugby game - it can be passed 'on foot' hand to hand or it can be thrown
> over intermediate players to its final destination (only with errors its in
> reverse - they are mostly passed/returned or thrown 'backwards':).
> IOW things like error_info will be applicable to 'the' standardized error
> type(s) that functions also return, not just throw.
>

You could theoretically pass exceptions function-to-function by
exception_ptr but that seems backwards, no pun intended. :)

Anyway, my point is that there are two ways one can go about solving the
problem of transporting error codes. One is what seems to be the path
you're taking, where you design a common API that can fit all error codes
on all platforms. On the other hand, the Boost Exception approach sidesteps
this difficult problem.

Let's take socket errors for example, which on POSIX are communicated by
the OS through errno, while on Windows there is the GetLastError function.
Using the Boost Exception approach, you would always store the errno into
any socket exception object, and on Windows you'd also store the
GetLastError code. At the catch site, regardless of the platform, you can
put in logic that takes correct action based on the presence or absence of
any particular error_info:

catch( socket_error & e )
{
int const * e1=get_error_info<errinfo_errno>(e);
assert(e1!=0); //errno must be present in all socket_error objects
if( int const * e2=get_error_info<errinfo_GetLastError>(e) )
{
//Both errno (*e1) and GetLastError (*e2) codes available.
}
else
{
//No GetLastError, use errno only.
}
}

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas
2016-01-29 13:37:27 UTC
Permalink
On 28 Jan 2016 at 16:57, Emil Dotchevski wrote:

> Let's take socket errors for example, which on POSIX are communicated by
> the OS through errno, while on Windows there is the GetLastError function.
> Using the Boost Exception approach, you would always store the errno into
> any socket exception object, and on Windows you'd also store the
> GetLastError code. At the catch site, regardless of the platform, you can
> put in logic that takes correct action based on the presence or absence of
> any particular error_info:

Doesn't std::system_error already do this for us, and it's already in
the standard and it also allows arbitrary domain specific error
coding?

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/
http://ie.linkedin.com/in/nialldouglas/
Emil Dotchevski
2016-01-29 23:47:20 UTC
Permalink
On Fri, Jan 29, 2016 at 5:37 AM, Niall Douglas <***@nedprod.com>
wrote:

> On 28 Jan 2016 at 16:57, Emil Dotchevski wrote:
>
> > Let's take socket errors for example, which on POSIX are communicated by
> > the OS through errno, while on Windows there is the GetLastError
> function.
> > Using the Boost Exception approach, you would always store the errno into
> > any socket exception object, and on Windows you'd also store the
> > GetLastError code. At the catch site, regardless of the platform, you can
> > put in logic that takes correct action based on the presence or absence
> of
> > any particular error_info:
>
> Doesn't std::system_error already do this for us, and it's already in
> the standard and it also allows arbitrary domain specific error
> coding?
>

My point is that perhaps it isn't arbitrary enough because there may be a
lot more than errno and GetLastError that is relevant to a given failure.
In the case of sockets, it may be necessary to transport a relevant URL to
the catch point, yet information like this isn't always available at the
point of the throw.

Obviously this is beyond the scope of std::system_error, I was just saying
that really exceptions or other error-reporting objects need to be able to
transport anything at all, especially in library-level code.

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas
2016-01-30 14:51:26 UTC
Permalink
On 29 Jan 2016 at 15:47, Emil Dotchevski wrote:

> > Doesn't std::system_error already do this for us, and it's already in
> > the standard and it also allows arbitrary domain specific error
> > coding?
> >
>
> My point is that perhaps it isn't arbitrary enough because there may be a
> lot more than errno and GetLastError that is relevant to a given failure.
> In the case of sockets, it may be necessary to transport a relevant URL to
> the catch point, yet information like this isn't always available at the
> point of the throw.

It's a fair point. I simply subclassed the exception type in question
and made it happy to get type sliced, but I'll grant you that
probably isn't best provided to library end users who tend to get
type slicing wrong as it is a bit brittle.

> Obviously this is beyond the scope of std::system_error, I was just saying
> that really exceptions or other error-reporting objects need to be able to
> transport anything at all, especially in library-level code.

Something I've always wished for is for std::exception to be able to
transport one or more stack backtraces. I rolled my own for AFIO v1
where it captures the stack both inside the engine and the stack
where end user code called AFIO code (both were always in different
threads), but it's a lot of code and is not efficient, and sadly will
not be present in AFIO v2 which is 98% noexcept and single threaded
throughout.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/
http://ie.linkedin.com/in/nialldouglas/
Emil Dotchevski
2016-01-30 19:41:34 UTC
Permalink
On Sat, Jan 30, 2016 at 6:51 AM, Niall Douglas <***@nedprod.com>
wrote:

> On 29 Jan 2016 at 15:47, Emil Dotchevski wrote:
> > Obviously this is beyond the scope of std::system_error, I was just
> saying
> > that really exceptions or other error-reporting objects need to be able
> to
> > transport anything at all, especially in library-level code.
>
> Something I've always wished for is for std::exception to be able to
> transport one or more stack backtraces. I rolled my own for AFIO v1
> where it captures the stack both inside the engine and the stack
> where end user code called AFIO code (both were always in different
> threads), but it's a lot of code and is not efficient, and sadly will
> not be present in AFIO v2 which is 98% noexcept and single threaded
> throughout.
>

Backtraces aren't enough. It is necessary for exception objects to be able
to transport arbitrary data, though of course one of the things that would
make sense to transport is a stack trace. I had proposed
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3757.html, which
was turned down because it was ruled that it requires a change in the
(std::exception) ABI. I am pretty sure that thread local storage can be
used to implement N3757 without ABI changes but I haven't got around to try
to implement it.

If exception objects can transport any data whatsoever, then there is no
need to worry whether individual data, like system error codes, are
sufficiently flexible.

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Gavin Lambert
2016-02-03 00:14:08 UTC
Permalink
On 31/01/2016 03:51, Niall Douglas wrote:
> Something I've always wished for is for std::exception to be able to
> transport one or more stack backtraces. I rolled my own for AFIO v1
> where it captures the stack both inside the engine and the stack
> where end user code called AFIO code (both were always in different
> threads), but it's a lot of code and is not efficient, and sadly will
> not be present in AFIO v2 which is 98% noexcept and single threaded
> throughout.

FWIW, through some compiler-specific chicanery you can recover a stack
trace (for the latest throw site) from any in-flight exception
(including system exceptions such as access violations) in MSVC+Windows
at least. I've found that to be incredibly helpful in the past, even if
only applied to crashes during unit tests. ;)

I haven't checked if it's possible to do something similar in gcc/clang
and/or Linux, but I would hope so.



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Domagoj Saric
2016-02-04 22:02:55 UTC
Permalink
On 29.1.2016. 1:57, Emil Dotchevski wrote:
> On Thu, Jan 28, 2016 at 3:44 PM, Domagoj Saric <***@gmail.com>
> wrote:
>> Hopefully these efforts will bring us to a solution that will do
>> away the, IMO, false dichotomy between "errors" and "exceptions"
>> (<system_error> vs <exception>) - rather we will start thinking
>> only about "errors"/error objects which can be transfered in two
>> different ways - like a ball in a rugby game - it can be passed
>> 'on foot' hand to hand or it can be thrown over intermediate
>> players to its final destination (only with errors its in reverse -
>> they are mostly passed/returned or thrown 'backwards':). IOW things
>> like error_info will be applicable to 'the' standardized error
>> type(s) that functions also return, not just throw.
>>
>
> You could theoretically pass exceptions function-to-function by
> exception_ptr but that seems backwards, no pun intended. :)

And we could theoretically and practically ;) return actual error
objects (even those considered 'exceptions', e.g. std::runtime_error)
w/o necessarily stuffing them into some type erasing mechanism like
exception_ptr (i.e. you pass the ball as a ball until you need to
interact with someone who only understands abstract UFOs;)


> Anyway, my point is that there are two ways one can go about solving
> the problem of transporting error codes. One is what seems to be the
> path you're taking, where you design a common API that can fit all
> error codes on all platforms. On the other hand, the Boost Exception
> approach sidesteps this difficult problem.

That's a separate issue. The "expected/result/etc" debate&proposals are
first and foremost about the ('all new and improved') mechanism for
passing errors around. The problem of how to abstract semantically
'similar' but syntactically 'not the same' error reporting mechanisms
across platforms (such as your errno vs win32 last error code example)
is orthogonal to this. However I do think that that problem should also
be solved by the same library as the first one, i.e. that an err-like
library would offer a std::result-like 'synthesis' of the 'error codes'
vs 'exceptions' dialectic _and_ offer wrappers, abstractions and
utilities to work with platform errors (e.g. NTSTATUS, HRESULT, win32
last error, errno[1], OSX OSStatus, ObjC exceptions, JNI etc.) and on
top of that generalized Boost.Exception-like functionality - IOW a
complete "error library"...


[1]
https://github.com/psiha/err/blob/master/include/boost/err/errno.hpp
https://github.com/psiha/err/blob/master/include/boost/err/win32.hpp


--
"What Huxley teaches is that in the age of advanced technology,
spiritual devastation is more likely to come from an enemy with a
smiling face than from one whose countenance exudes suspicion and hate."
Neil Postman


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas
2016-02-05 09:25:09 UTC
Permalink
On 4 Feb 2016 at 23:02, Domagoj Saric wrote:

> > You could theoretically pass exceptions function-to-function by
> > exception_ptr but that seems backwards, no pun intended. :)
>
> And we could theoretically and practically ;) return actual error
> objects (even those considered 'exceptions', e.g. std::runtime_error)
> w/o necessarily stuffing them into some type erasing mechanism like
> exception_ptr (i.e. you pass the ball as a ball until you need to
> interact with someone who only understands abstract UFOs;)

exception_ptr is like shared_ptr - the potential use of atomics
forces the compiler to emit code just in case. Any potential use of
atomics is like calling fsync() on the compiler's AST.

> https://github.com/psiha/err/blob/master/include/boost/err/errno.hpp
> https://github.com/psiha/err/blob/master/include/boost/err/win32.hpp

Firstly I just found and read your RFC on this potential Boost.Err at
http://lists.boost.org/Archives/boost/2015/11/226558.php. You posted
this during my vacation away from Boost, so I didn't see it till now.
I'd like to thank you for this contribution to the debate, it
certainly is original.

There are some very interesting - perhaps even debatable - feature
choices in this object. I'll skip commenting on most of those, but I
will say one thing - I've found in AFIO v2 the ability to use
outcome::result<T> as a receiving container surprisingly useful. The
pattern looks like this:

1. User hands some scatter buffers to be filled by a
file_handle::async_read() to AFIO v2. These probably are a
std::vector<std::pair<char *, size_t>>.

2. AFIO dynamically allocates a file_handle::io_state to represent
the i/o (memory allocation is unavoidable, but in AFIO v2 there is
exactly one memory allocation and deallocation per i/o now, unlike
eight allocations and deallocations in v1) and move constructs the
user's scatter buffers into an
outcome::result<std::vector<std::pair<char *, size_t>>> living inside
the file_handle::io_state.

3. For each completing buffer in the scatter list, if there was an
error we set the outcome::result<> to that error. If not errored and
the outcome::result<> does not hold an error, we update the buffer
with the transfer achieved.

4. Once all buffers are completed, we invoke whatever completion
handler of type U&& the user specified, passing through the
file_handle::io_state.


In other words, I found my result<T> very useful as a state
accumulator which can "go errored" in a natural way. Some may note
that we throw away all errors but the first received one, but that's
fine in this use case - I cannot think of a use case where an end
user cares if parts of a scatter-gather operation succeed and others
fail, if they did they'd split up the scatter-gather list into
separate operations.

Your Err object can't do value semantics - it cannot be a container
in its own right. I personally think that is unfortunate for the
reasons above. I think there is much more usefulness in these objects
having full value semantics BUT with a restricted variant content, so
basically either it's the value you want OR it's why there isn't a
value you expect there.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/
http://ie.linkedin.com/in/nialldouglas/
Domagoj Saric
2016-02-07 17:49:27 UTC
Permalink
On 5.2.2016. 10:25, Niall Douglas wrote:
> On 4 Feb 2016 at 23:02, Domagoj Saric wrote:
>
>>> You could theoretically pass exceptions function-to-function by
>>> exception_ptr but that seems backwards, no pun intended. :)
>>
>> And we could theoretically and practically ;) return actual error
>> objects (even those considered 'exceptions', e.g. std::runtime_error)
>> w/o necessarily stuffing them into some type erasing mechanism like
>> exception_ptr (i.e. you pass the ball as a ball until you need to
>> interact with someone who only understands abstract UFOs;)
>
> exception_ptr is like shared_ptr - the potential use of atomics
> forces the compiler to emit code just in case. Any potential use of
> atomics is like calling fsync() on the compiler's AST.

That's (in line with/part of) my point?


> Firstly I just found and read your RFC on this potential Boost.Err at
> http://lists.boost.org/Archives/boost/2015/11/226558.php. You posted
> this during my vacation away from Boost, so I didn't see it till now.
> I'd like to thank you for this contribution to the debate, it
> certainly is original.
>
> There are some very interesting - perhaps even debatable - feature
> choices in this object. I'll skip commenting on most of those, but I
> will say one thing - I've found in AFIO v2 the ability to use
> outcome::result<T> as a receiving container surprisingly useful. The
> pattern looks like this:
<snip>
> In other words, I found my result<T> very useful as a state
> accumulator which can "go errored" in a natural way.

Can you elaborate more on what you mean by "state accumulator" and
"going errored in a natural way" in this context? + you seem to be using
'container' and 'accumulator' interchangeably while AFAIK those are not
eqivalent concepts...

If by 'state accumulation' you imply the requirement that the result<T,
E> object (R from now on, as a short unifying 'meta name' for this
'result' object that we've all named differently) be mutable either in
the limited sense (only that the contained error, E, object, if any, be
mutable) or in the full sense (that the R object can be switched from a
'succesfull-so-far' to a failed state, i.e. that the contained T can be
destroyed and an E constructed in its place) then yes Err's
result_or_error and fallible_result do not support it:
- in the case of fallible_result, as an 'rvalue only' type, this makes
no sense
- in the case of result_or_error this is, for the most part, merely an
API decision/restriction.

For the 'limited sense' I can merely add the non-const "Error &
result_or_error<>::error()" getter so one can manipulate the contained
error object (if there is one, i.e. if the RO is in the failed state).
For the 'full sense' I'd have to make the result_or_error object
assignable, which in turn would require making the state flags non-const
(and hope this does not affect the optimiser).
However, the reason things are the way they are is narrowing the scope:
the two central Err types are meant to solve one, and only one, problem:
providing a generic, efficient and safe packaging for transporting
function call results (failed or succesfull) and are always meant to be
part of a return statement.
Cumulative and/or asynchronous results/operations (future-promise kind
of thingies) are IMO better implemented on-top of such lower level
primitives - i.e. concepts like fallible_result should not even
implicitly mention/model in their API such specialized solutions
(cumulative, async, etc. result) but should enable their efficient
implementation...


> Your Err object can't do value semantics - it cannot be a container
> in its own right. I personally think that is unfortunate for the
> reasons above. I think there is much more usefulness in these objects
> having full value semantics BUT with a restricted variant content,

I don't know what you mean here:
* there is no 'Err object', there are two types - fallible_result and
result_or_error[1]
* both of those carry Ts and Es by value


> so
> basically either it's the value you want OR it's why there isn't a
> value you expect there.

AFAICT that's exactly how the two Err objects work..?


[1] it 'dawned' on me a few days ago that the langauge change/addition I
already 'requested'/wished for (rvalue destructor 'overloads') would
also enable that the two types (fallible_result and result_or_error),
could be merged (as esentially their only major difference is
on-destruction behaviour)...


--
"What Huxley teaches is that in the age of advanced technology,
spiritual devastation is more likely to come from an enemy with a
smiling face than from one whose countenance exudes suspicion and hate."
Neil Postman



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Vicente J. Botet Escriba
2016-02-06 11:53:39 UTC
Permalink
Le 04/02/2016 23:02, Domagoj Saric a écrit :
> On 29.1.2016. 1:57, Emil Dotchevski wrote:
>> On Thu, Jan 28, 2016 at 3:44 PM, Domagoj Saric <***@gmail.com>
>> wrote:
>>> Hopefully these efforts will bring us to a solution that will do
>>> away the, IMO, false dichotomy between "errors" and "exceptions"
>>> (<system_error> vs <exception>) - rather we will start thinking
>>> only about "errors"/error objects which can be transfered in two
>>> different ways - like a ball in a rugby game - it can be passed
>>> 'on foot' hand to hand or it can be thrown over intermediate
>>> players to its final destination (only with errors its in reverse -
>>> they are mostly passed/returned or thrown 'backwards':). IOW things
>>> like error_info will be applicable to 'the' standardized error
>>> type(s) that functions also return, not just throw.
>>>
>>
>> You could theoretically pass exceptions function-to-function by
>> exception_ptr but that seems backwards, no pun intended. :)
>
> And we could theoretically and practically ;) return actual error
> objects (even those considered 'exceptions', e.g. std::runtime_error)
> w/o necessarily stuffing them into some type erasing mechanism like
> exception_ptr (i.e. you pass the ball as a ball until you need to
> interact with someone who only understands abstract UFOs;)
>
>
>> Anyway, my point is that there are two ways one can go about solving
>> the problem of transporting error codes. One is what seems to be the
>> path you're taking, where you design a common API that can fit all
>> error codes on all platforms. On the other hand, the Boost Exception
>> approach sidesteps this difficult problem.
>
> That's a separate issue. The "expected/result/etc" debate&proposals
> are first and foremost about the ('all new and improved') mechanism
> for passing errors around. The problem of how to abstract semantically
> 'similar' but syntactically 'not the same' error reporting mechanisms
> across platforms (such as your errno vs win32 last error code example)
> is orthogonal to this. However I do think that that problem should
> also be solved by the same library as the first one, i.e. that an
> err-like library would offer a std::result-like 'synthesis' of the
> 'error codes' vs 'exceptions' dialectic _and_ offer wrappers,
> abstractions and utilities to work with platform errors (e.g.
> NTSTATUS, HRESULT, win32 last error, errno[1], OSX OSStatus, ObjC
> exceptions, JNI etc.) and on top of that generalized
> Boost.Exception-like functionality - IOW a complete "error library"...
>
Hi,

in case this could help. There were two reports on this subject.
"Handling Disappointment in C++" by L. Crowl [1] and the other in the
draft state "Survey of Error Handling" by N. Bolas [2].

Best,
Vicente

[1] Handling Disappointment in C++
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0157r0.html

[2] Survey of Error Handling
https://11080372623597421729.googlegroups.com/attach/a8bf61b2beb10bec/Survey%20of%20Error%20Handling.html?part=0.1&view=1&vt=ANaJVrGXbT5TGHEPTZWW_iduAUmJBNCyB6yvlV3L0LQk5eZ3dsCSBPSx0hql8fZ3MMCGjw-xBx8bIt6e4-4A0q_fE1U_7jREulA6RbE2Roh4sHOov8TUMtw


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Emil Dotchevski
2016-02-06 20:48:26 UTC
Permalink
On Sat, Feb 6, 2016 at 3:53 AM, Vicente J. Botet Escriba <
***@wanadoo.fr> wrote:

> Le 04/02/2016 23:02, Domagoj Saric a écrit :
>
>> On 29.1.2016. 1:57, Emil Dotchevski wrote:
>>
>>> On Thu, Jan 28, 2016 at 3:44 PM, Domagoj Saric <***@gmail.com>
>>> wrote:
>>>
>>>> Hopefully these efforts will bring us to a solution that will do
>>>> away the, IMO, false dichotomy between "errors" and "exceptions"
>>>> (<system_error> vs <exception>) - rather we will start thinking
>>>> only about "errors"/error objects which can be transfered in two
>>>> different ways - like a ball in a rugby game - it can be passed
>>>> 'on foot' hand to hand or it can be thrown over intermediate
>>>> players to its final destination (only with errors its in reverse -
>>>> they are mostly passed/returned or thrown 'backwards':). IOW things
>>>> like error_info will be applicable to 'the' standardized error
>>>> type(s) that functions also return, not just throw.
>>>>
>>>>
>>> You could theoretically pass exceptions function-to-function by
>>> exception_ptr but that seems backwards, no pun intended. :)
>>>
>>
>> And we could theoretically and practically ;) return actual error
>> objects (even those considered 'exceptions', e.g. std::runtime_error)
>> w/o necessarily stuffing them into some type erasing mechanism like
>> exception_ptr (i.e. you pass the ball as a ball until you need to
>> interact with someone who only understands abstract UFOs;)
>>
>>
>> Anyway, my point is that there are two ways one can go about solving
>>> the problem of transporting error codes. One is what seems to be the
>>> path you're taking, where you design a common API that can fit all
>>> error codes on all platforms. On the other hand, the Boost Exception
>>> approach sidesteps this difficult problem.
>>>
>>
>> That's a separate issue. The "expected/result/etc" debate&proposals are
>> first and foremost about the ('all new and improved') mechanism for passing
>> errors around. The problem of how to abstract semantically 'similar' but
>> syntactically 'not the same' error reporting mechanisms across platforms
>> (such as your errno vs win32 last error code example) is orthogonal to
>> this. However I do think that that problem should also be solved by the
>> same library as the first one, i.e. that an err-like library would offer a
>> std::result-like 'synthesis' of the 'error codes' vs 'exceptions' dialectic
>> _and_ offer wrappers, abstractions and utilities to work with platform
>> errors (e.g. NTSTATUS, HRESULT, win32 last error, errno[1], OSX OSStatus,
>> ObjC exceptions, JNI etc.) and on top of that generalized
>> Boost.Exception-like functionality - IOW a complete "error library"...
>>
>> <snip>
> [1] Handling Disappointment in C++
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0157r0.html
>

Generally I take issue with claims that exception handling overhead can
cause performance problems. The linked paper mentions one of the problems
being variance in execution time between the error-no error execution
paths, which can't be addressed anyway since detecting an error early on in
an attempted operation may skip significant amount of code. Another stated
concern is that exception handling introduces overhead throughout the
program even when exceptions are not being propagated, however such
overhead (if any) is limited to function call and exit points. In
performance-critical contexts function calls are too expensive with or
without the added overhead of exception handling, and the solution is to
inline the function. In that case all function call overhead disappears,
including the exception handling overhead.

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.o
Domagoj Saric
2016-02-07 19:02:47 UTC
Permalink
On 6.2.2016. 21:48, Emil Dotchevski wrote:
> Another stated
> concern is that exception handling introduces overhead throughout the
> program even when exceptions are not being propagated, however such
> overhead (if any) is limited to function call and exit points. In
> performance-critical contexts function calls are too expensive with or
> without the added overhead of exception handling, and the solution is to
> inline the function. In that case all function call overhead disappears,
> including the exception handling overhead.

However
* this reasoning is only valid (and even then not fully) in synthetic
tests/non-real world analysis - i.e. 'my program is the only one on the
system' - on a real system, this reasoning is merely the number one
licence to produce bloatware as there everything is 'performance
critical' - your 'non critical' (i.e. fatter and slower than necessary)
code (in addition to contributing to OTA and flash storage costs, IO,
fragmentation...) might be loaded and/or executed (or simply occupy the
virtual address space) at the same time that another app is trying to
"squeeze the most of a user's device" (i.e. running its 'critical' part)
* inlining is, in general, a primitve bruteforce solution that can only
exacerbate the problem of bloatware (discussed this in more detail
relatively recently with Andrey Semashev)
* even the very latest MSVC compiler cannot inline functions that
contain certain types of EH (even something simple as having a by-value
parameter that has a non-trivial destructor)...


--
"What Huxley teaches is that in the age of advanced technology,
spiritual devastation is more likely to come from an enemy with a
smiling face than from one whose countenance exudes suspicion and hate."
Neil Postman


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Emil Dotchevski
2016-02-07 21:21:07 UTC
Permalink
On Sun, Feb 7, 2016 at 11:02 AM, Domagoj Saric <***@gmail.com> wrote:

> On 6.2.2016. 21:48, Emil Dotchevski wrote:
>
>> Another stated
>> concern is that exception handling introduces overhead throughout the
>> program even when exceptions are not being propagated, however such
>> overhead (if any) is limited to function call and exit points. In
>> performance-critical contexts function calls are too expensive with or
>> without the added overhead of exception handling, and the solution is to
>> inline the function. In that case all function call overhead disappears,
>> including the exception handling overhead.
>>
>
> However
> * this reasoning is only valid (and even then not fully) in synthetic
> tests/non-real world analysis - i.e. 'my program is the only one on the
> system' - on a real system, this reasoning is merely the number one licence
> to produce bloatware as there everything is 'performance critical' - your
> 'non critical' (i.e. fatter and slower than necessary) code (in addition to
> contributing to OTA and flash storage costs, IO, fragmentation...) might be
> loaded and/or executed (or simply occupy the virtual address space) at the
> same time that another app is trying to "squeeze the most of a user's
> device" (i.e. running its 'critical' part)
>

There are upsides to "slower" code. Are you saying that *your* code can't
be optimized any further? :) Or is it that you've chosen to draw the line
arbitrarily at "exception handling is too slow"?

Code size does matter on a system with, say, 32 megs of ram, like the
Playstation 2. If, on a modern system, the size of your code is the reason
why you're running out of memory or address space, exception handling
overhead is the least of your worries. :)


> * inlining is, in general, a primitve bruteforce solution that can only
> exacerbate the problem of bloatware (discussed this in more detail
> relatively recently with Andrey Semashev)
>

Indeed, inlining shouldn't be used throughout the code, to avoid bloat and
for many other reasons. When you can afford the cost of a function call you
should opt to pay it because this reduces physical coupling.


> * even the very latest MSVC compiler cannot inline functions that contain
> certain types of EH (even something simple as having a by-value parameter
> that has a non-trivial destructor)...
>

So? Either you can afford the function call, or you must tweak the code
until the call disappears.

The hard fact in discussions about exception handling overhead is that
either 1) you can afford it or 2) you can easily remove it selectively, by
using inline.

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Michael Marcin
2016-02-09 02:21:18 UTC
Permalink
On 2/7/2016 3:21 PM, Emil Dotchevski wrote:
> On Sun, Feb 7, 2016 at 11:02 AM, Domagoj Saric <***@gmail.com> wrote:
>
>> On 6.2.2016. 21:48, Emil Dotchevski wrote:
>>
>>> Another stated
>>> concern is that exception handling introduces overhead throughout the
>>> program even when exceptions are not being propagated, however such
>>> overhead (if any) is limited to function call and exit points. In
>>> performance-critical contexts function calls are too expensive with or
>>> without the added overhead of exception handling, and the solution is to
>>> inline the function. In that case all function call overhead disappears,
>>> including the exception handling overhead.
>>>
>>
>> However
>> * this reasoning is only valid (and even then not fully) in synthetic
>> tests/non-real world analysis - i.e. 'my program is the only one on the
>> system' - on a real system, this reasoning is merely the number one licence
>> to produce bloatware as there everything is 'performance critical' - your
>> 'non critical' (i.e. fatter and slower than necessary) code (in addition to
>> contributing to OTA and flash storage costs, IO, fragmentation...) might be
>> loaded and/or executed (or simply occupy the virtual address space) at the
>> same time that another app is trying to "squeeze the most of a user's
>> device" (i.e. running its 'critical' part)
>>
>
> There are upsides to "slower" code. Are you saying that *your* code can't
> be optimized any further? :) Or is it that you've chosen to draw the line
> arbitrarily at "exception handling is too slow"?
>
> Code size does matter on a system with, say, 32 megs of ram, like the
> Playstation 2. If, on a modern system, the size of your code is the reason
> why you're running out of memory or address space, exception handling
> overhead is the least of your worries. :)
>
>
>> * inlining is, in general, a primitve bruteforce solution that can only
>> exacerbate the problem of bloatware (discussed this in more detail
>> relatively recently with Andrey Semashev)
>>
>
> Indeed, inlining shouldn't be used throughout the code, to avoid bloat and
> for many other reasons. When you can afford the cost of a function call you
> should opt to pay it because this reduces physical coupling.
>
>
>> * even the very latest MSVC compiler cannot inline functions that contain
>> certain types of EH (even something simple as having a by-value parameter
>> that has a non-trivial destructor)...
>>
>
> So? Either you can afford the function call, or you must tweak the code
> until the call disappears.
>
> The hard fact in discussions about exception handling overhead is that
> either 1) you can afford it or 2) you can easily remove it selectively, by
> using inline.
>

I'm not sure why the topic has moved into a discussion of the overhead
and performance of exception handling. I think the performance
characteristics of EH are pretty well understood (at least around these
hallowed halls).

Users who want a result<T> abstraction however are not interested in a
solution like EH which is heavily biased in performance towards the
non-error control flow.

Errors reported through a result<T> abstraction are usually not
exceptional and I would expect comparable performance between the error
and non-error control flow.

Using result<T> doesn't preclude exception handling. I think they work
very well together.

i.e. in the case of text entry you might return a result<std::string>
valid text would return directly
invalid text would return an error code
failure to allocate memory for the std::string would throw an exception


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Emil Dotchevski
2016-02-09 07:27:09 UTC
Permalink
On Mon, Feb 8, 2016 at 6:21 PM, Michael Marcin <***@gmail.com>
wrote:

> Using result<T> doesn't preclude exception handling. I think they work
> very well together.
>

If you mean that they can work together within the same program, that's
obviously true. At some level it's all C and it's all error codes, so
arguably you can't escape them.

But of course the two approaches use completely different strategies. In
one case the programmer has to write if( error ) return error, like a
neanderthal. In the other, he has to write exception-safe code but the
upside is less error handling bugs, which is important as error handling
code is notoriously difficult to test.

Yes, with exceptions you do get different performance between the error-no
error paths, and that is critical in some applications, but these are
extremely specialized. In these cases one can't use dynamic memory
allocations either, which renders a lot of standard C and C++ functions off
limits, not only exception handling.


> i.e. in the case of text entry you might return a result<std::string>
> valid text would return directly
> invalid text would return an error code
> failure to allocate memory for the std::string would throw an exception


In the wild I've never seen a bad_alloc from std::string, except when it
was caused by a logic error in my program. If exceptions are used just so
that std::string can throw bad_alloc, that's 1) not worth the overhead of
exception handling and 2) extremely difficult to test..

Besides, in the case of text entry I doubt very much that the difference in
performance between the error-no error paths would matter in practice.

Of course sometimes you may need to distinguish between "nothing entered"
and "empty string entered" without signalling an illegal entry. In this
case you can simply return shared_ptr<std::string>, and throw only in case
of illegal entry.

Cheers,
Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas
2016-02-09 09:16:34 UTC
Permalink
On 8 Feb 2016 at 23:27, Emil Dotchevski wrote:

> But of course the two approaches use completely different strategies. In
> one case the programmer has to write if( error ) return error, like a
> neanderthal. In the other, he has to write exception-safe code but the
> upside is less error handling bugs, which is important as error handling
> code is notoriously difficult to test.

In practice if you're returning an important outcome from a function
then you'll always be using it, so you don't notice the fact you're
checking return values as much. Still, I agree that result<void> is
as easy to forget about as an int return code, and propagating
errored outcomes up the call stack is unavoidably manual (though
macro helpers make it a one line exercise). It's still vastly more
powerful and convenient to write than int error code returns a la C,
and future clang-tidy tooling can help out a lot on avoiding mistakes
unlike C int error code returns which could mean anything, and
therefore cannot be AST rewritten.

Something missing from the discussion so far is that
expected/result/outcome MUST throw exceptions! If you try to fetch a
value from a result and it contains an error, there is no alternative
to throwing an exception. This fact is why I don't worry about static
function initialisers and just go ahead and use
error-code-via-system_error throwing constructors, ultimately you
need try...catch in there one way or another.

I would therefore not draw as thick a line between the two approaches
as some here are doing. For systems programming where you are a thin
veneer around OS calls a 98% result based noexcept programming style
is the optimal way of doing this sort of C++ programming - tiny
runtime overhead, powerful abstraction over C error code programming,
highly maintainable and extremely amenable to static analysis to
catch logic errors. With just a little extra libclang tooling (some
of which I plan to write) this style idiom ought to be mathematically
provable as correct in the functional programming sense, which would
be cool, not least for those programming nuclear reactors etc.

The further you get away from errno/GetLastError() though the more
you end up exclusively on C++ exceptions which is no bad thing. Large
teams of engineers make better code more cheaply when the language
does exceptions, though the skills demands of writing exception safe
C++ is non trivial. Still, as one can see in LLVM's universal
exception handling framework, C++ exception throws ought to
seamlessly convert into other LLVM based languages, so potentially a
big future win there: write just enough in C++, write the rest in
some language average programmers do better with, both layers
seamlessly work together.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/
http://ie.linkedin.com/in/nialldouglas/
Vicente J. Botet Escriba
2016-02-09 16:50:01 UTC
Permalink
Le 09/02/2016 10:16, Niall Douglas a écrit :
> On 8 Feb 2016 at 23:27, Emil Dotchevski wrote:
>
>> But of course the two approaches use completely different strategies. In
>> one case the programmer has to write if( error ) return error, like a
>> neanderthal. In the other, he has to write exception-safe code but the
>> upside is less error handling bugs, which is important as error handling
>> code is notoriously difficult to test.
> In practice if you're returning an important outcome from a function
> then you'll always be using it, so you don't notice the fact you're
> checking return values as much. Still, I agree that result<void> is
> as easy to forget about as an int return code, and propagating
> errored outcomes up the call stack is unavoidably manual (though
> macro helpers make it a one line exercise). It's still vastly more
> powerful and convenient to write than int error code returns a la C,
> and future clang-tidy tooling can help out a lot on avoiding mistakes
> unlike C int error code returns which could mean anything, and
> therefore cannot be AST rewritten.
>
> Something missing from the discussion so far is that
> expected/result/outcome MUST throw exceptions! If you try to fetch a
> value from a result and it contains an error, there is no alternative
> to throwing an exception.
Yes there is one, to don't try to obtain the value directly with a
get/value function. If you don't want to use exceptions, you should use
visitation, functor/monad interfaces which are always safe.

No need to write anymore neanderthal code like if(error) ;-)

Vicente

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Emil Dotchevski
2016-02-09 20:04:44 UTC
Permalink
On Tue, Feb 9, 2016 at 1:16 AM, Niall Douglas <***@nedprod.com>
wrote:

> On 8 Feb 2016 at 23:27, Emil Dotchevski wrote:
>
> > But of course the two approaches use completely different strategies. In
> > one case the programmer has to write if( error ) return error, like a
> > neanderthal. In the other, he has to write exception-safe code but the
> > upside is less error handling bugs, which is important as error handling
> > code is notoriously difficult to test.
> <deleted>
> I would therefore not draw as thick a line between the two approaches
> as some here are doing. For systems programming where you are a thin
> veneer around OS calls a 98% result based noexcept programming style
> is the optimal way of doing this sort of C++ programming - tiny
> runtime overhead, powerful abstraction over C error code programming,
> highly maintainable and extremely amenable to static analysis to
> catch logic errors.
>

Lots of adjectives, perhaps we should discuss each one of them? :)

The use of error codes in low level APIs is a fact of life, since most of
that code is written in C anyway. I wouldn't call it optimal in C++, I
personally convert error codes into exceptions at the earliest possible
opportunity. As for runtime overhead, like I said already, where it matters
it can be easily deleted, by inlining; and, in such contexts you need to
inline with or without exception handling: I've never seen a case where I
could afford the overhead of a function call but could not afford the
exception handling overhead in the same function.

It feels strange to have to defend the use of exceptions for reporting
errors in C++, on the boost development board of all places. There are many
other advantages, for example when returning errors there is no such thing
as error-neutral contexts in your program, which increases coupling. Yes,
in some contexts one can't afford to use exceptions, but all general
complains that exception handling causes performance or any other problems
are theoretical, at best.


> With just a little extra libclang tooling (some
> of which I plan to write) this style idiom ought to be mathematically
> provable as correct in the functional programming sense, which would
> be cool, not least for those programming nuclear reactors etc.
>

Could you prove anything mathematically in the presence of side effects and
pointers?

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Hartmut Kaiser
2016-02-09 22:50:48 UTC
Permalink
> > > But of course the two approaches use completely different strategies.
> In
> > > one case the programmer has to write if( error ) return error, like a
> > > neanderthal. In the other, he has to write exception-safe code but the
> > > upside is less error handling bugs, which is important as error
> handling
> > > code is notoriously difficult to test.
> > <deleted>
> > I would therefore not draw as thick a line between the two approaches
> > as some here are doing. For systems programming where you are a thin
> > veneer around OS calls a 98% result based noexcept programming style
> > is the optimal way of doing this sort of C++ programming - tiny
> > runtime overhead, powerful abstraction over C error code programming,
> > highly maintainable and extremely amenable to static analysis to
> > catch logic errors.
> >
>
> Lots of adjectives, perhaps we should discuss each one of them? :)
>
> The use of error codes in low level APIs is a fact of life, since most of
> that code is written in C anyway. I wouldn't call it optimal in C++, I
> personally convert error codes into exceptions at the earliest possible
> opportunity. As for runtime overhead, like I said already, where it
> matters
> it can be easily deleted, by inlining; and, in such contexts you need to
> inline with or without exception handling: I've never seen a case where I
> could afford the overhead of a function call but could not afford the
> exception handling overhead in the same function.
>
> It feels strange to have to defend the use of exceptions for reporting
> errors in C++, on the boost development board of all places. There are
> many
> other advantages, for example when returning errors there is no such thing
> as error-neutral contexts in your program, which increases coupling. Yes,
> in some contexts one can't afford to use exceptions, but all general
> complains that exception handling causes performance or any other problems
> are theoretical, at best.

I couldn't agree more. It is questionable at best to base design decisions
on the pseudo argument of 'exceptions incur too much overhead'. In my
experience, the benefits of using exceptions clearly outweighs any
disadvantages.

Regards Hartmut
---------------
http://boost-spirit.com
http://stellar.cct.lsu.edu




_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Gavin Lambert
2016-02-09 23:41:48 UTC
Permalink
On 10/02/2016 09:04, Emil Dotchevski wrote:
> The use of error codes in low level APIs is a fact of life, since most of
> that code is written in C anyway. I wouldn't call it optimal in C++, I
> personally convert error codes into exceptions at the earliest possible
> opportunity. As for runtime overhead, like I said already, where it matters
> it can be easily deleted, by inlining; and, in such contexts you need to
> inline with or without exception handling: I've never seen a case where I
> could afford the overhead of a function call but could not afford the
> exception handling overhead in the same function.

The overhead of exception-handling plumbing that is not actually
exercised is typically minimal and not worth worrying about unless
you're in one of those contexts where you begrudge every method call, as
you've said.

The overhead of a thrown exception is larger, particularly on those
systems where throwing an exception also captures a stack trace (which
is incredibly helpful for debugging, but sadly often quite expensive).

The overhead of dealing with error return codes for uncommon failures is
also fairly large, with the risk of ignoring them and ending up in weird
states.

The trick is to find the balance between them, with the complication
that it may not be known at the library level which failures are
exceptional and which are expected. As an example, I can imagine that
most applications don't want a "delete file" method to throw an
exception if the file is already absent -- but there might be some
transactional systems in which that would be a serious problem, and
checking for existence beforehand might not be sufficient as it would
still be a race condition.

This sort of thing could be a good argument for "degrees of success",
where eg. the above delete call returns success in both cases but can
also indicate that the file was already missing and it did nothing.
(Which is trivial in this case since it can be done with a simple bool
return value, but you can probably imagine other cases where it might
make sense to return both a T and a non-error status value of some kind.)



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Emil Dotchevski
2016-02-10 02:41:39 UTC
Permalink
On Tue, Feb 9, 2016 at 3:41 PM, Gavin Lambert <***@compacsort.com> wrote:

> On 10/02/2016 09:04, Emil Dotchevski wrote:
>
>> The use of error codes in low level APIs is a fact of life, since most of
>> that code is written in C anyway. I wouldn't call it optimal in C++, I
>> personally convert error codes into exceptions at the earliest possible
>> opportunity. As for runtime overhead, like I said already, where it
>> matters
>> it can be easily deleted, by inlining; and, in such contexts you need to
>> inline with or without exception handling: I've never seen a case where I
>> could afford the overhead of a function call but could not afford the
>> exception handling overhead in the same function.
>>
>
> The overhead of exception-handling plumbing that is not actually exercised
> is typically minimal and not worth worrying about unless you're in one of
> those contexts where you begrudge every method call, as you've said.
>
> The overhead of a thrown exception is larger, particularly on those
> systems where throwing an exception also captures a stack trace (which is
> incredibly helpful for debugging, but sadly often quite expensive).
>

True, but I'm yet to encounter a case where I'd care about that overhead.
And I don't particularly care how that time compares to the amount of time
the successful path takes, since both of them typically vary significantly
from one platform to another. Assuming we're dealing with an exceptional
situation (something went wrong), that's not a problem.

Besides, I'd happily agree -- in the presence of profiler evidence -- that
if in a given use case exception handling overhead causes problems which
can not be alleviated by inlining a few functions, it shouldn't be used.
This is not because returning error codes is "better": in some cases we
also do need to resort to writing hand-crafted platform-specific assembly,
but we shouldn't argue that we can't use C++ in general because of overhead.


> The overhead of dealing with error return codes for uncommon failures is
> also fairly large, with the risk of ignoring them and ending up in weird
> states.
>
> The trick is to find the balance between them, with the complication that
> it may not be known at the library level which failures are exceptional and
> which are expected. As an example, I can imagine that most applications
> don't want a "delete file" method to throw an exception if the file is
> already absent -- but there might be some transactional systems in which
> that would be a serious problem, and checking for existence beforehand
> might not be sufficient as it would still be a race condition.
>

This reasoning is completely orthogonal to the error reporting design -- it
has to do with correctly defining the postconditions of the function, which
as you point out is domain-specific. Exceptions are used specifically to
enforce postconditions. If the correct postcondition of delete_file is "the
file does not exist", then it is incorrect to throw if the file was missing
to begin with.


> This sort of thing could be a good argument for "degrees of success",
> where eg. the above delete call returns success in both cases but can also
> indicate that the file was already missing and it did nothing. (Which is
> trivial in this case since it can be done with a simple bool return value,
> but you can probably imagine other cases where it might make sense to
> return both a T and a non-error status value of some kind.)


Assuming the correct postcondition for delete_file is "the file does not
exist", and assuming that knowing if the file existed is either important
or trivial for delete_file to detect, the correct interface of that
function is:

//Postconditions: the specified file does not exist.
//Returns: true if the file was actually deleted, false if it did not exist
//Throws: delete_file_error
bool delete_file( char const * name );

This is not "degrees of success", as the condition in which the file did
not exist is not an error.

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Gavin Lambert
2016-02-10 03:43:30 UTC
Permalink
On 10/02/2016 15:41, Emil Dotchevski wrote:
>> This sort of thing could be a good argument for "degrees of success",
>> where eg. the above delete call returns success in both cases but can also
>> indicate that the file was already missing and it did nothing. (Which is
>> trivial in this case since it can be done with a simple bool return value,
>> but you can probably imagine other cases where it might make sense to
>> return both a T and a non-error status value of some kind.)
>
> Assuming the correct postcondition for delete_file is "the file does not
> exist", and assuming that knowing if the file existed is either important
> or trivial for delete_file to detect, the correct interface of that
> function is:
>
> //Postconditions: the specified file does not exist.
> //Returns: true if the file was actually deleted, false if it did not exist
> //Throws: delete_file_error
> bool delete_file( char const * name );
>
> This is not "degrees of success", as the condition in which the file did
> not exist is not an error.

As I said, that was a trivial case.

For a less trivial case, imagine a function that fills in a
variable-size data structure, but can also indicate that some of it was
truncated and supplying a larger buffer would get a more complete
result. (Sort of like strncpy, but more complex.)

Or a cursor enumeration function that wants to simultaneously return a
pageful of data and how many records are left unreported, so these don't
have to be checked separately, but the latter could be ignored in
contexts where it's not interesting.

Of course, these can be handled by making a larger return type T
(perhaps as a tuple), but this implies they have equal weight, which is
not necessarily the case. It's also brittle if a future version of the
method wants to add more information.

Having said that, I'm not entirely sure where I was going with this
topic. I might be rambling a bit.



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Emil Dotchevski
2016-02-10 19:40:26 UTC
Permalink
On Tue, Feb 9, 2016 at 7:43 PM, Gavin Lambert <***@compacsort.com> wrote:

> On 10/02/2016 15:41, Emil Dotchevski wrote:
>
>> This sort of thing could be a good argument for "degrees of success",
>>> where eg. the above delete call returns success in both cases but can
>>> also
>>> indicate that the file was already missing and it did nothing. (Which is
>>> trivial in this case since it can be done with a simple bool return
>>> value,
>>> but you can probably imagine other cases where it might make sense to
>>> return both a T and a non-error status value of some kind.)
>>>
>>
>> Assuming the correct postcondition for delete_file is "the file does not
>> exist", and assuming that knowing if the file existed is either important
>> or trivial for delete_file to detect, the correct interface of that
>> function is:
>>
>> //Postconditions: the specified file does not exist.
>> //Returns: true if the file was actually deleted, false if it did not
>> exist
>> //Throws: delete_file_error
>> bool delete_file( char const * name );
>>
>> This is not "degrees of success", as the condition in which the file did
>> not exist is not an error.
>>
>
> As I said, that was a trivial case.
>
> For a less trivial case, imagine a function that fills in a variable-size
> data structure, but can also indicate that some of it was truncated and
> supplying a larger buffer would get a more complete result. (Sort of like
> strncpy, but more complex.)
>
> Or a cursor enumeration function that wants to simultaneously return a
> pageful of data and how many records are left unreported, so these don't
> have to be checked separately, but the latter could be ignored in contexts
> where it's not interesting.
>

It doesn't matter what your use case is: while the success/failure
vocabulary isn't incorrect, it is more precise to think in terms of
postconditions. You call a function, it will either satisfy its
postconditions or it won't return. Sure, you can and should return all
information that the caller needs in order to proceed with whatever
operation is being attempted, but there is no need to return anything if
the operation can't proceed.

So all you need to do is correctly identify the conditions that indicate
that the operation can't proceed, and throw an exception.

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Gavin Lambert
2016-02-10 21:30:53 UTC
Permalink
On 11/02/2016 08:40, Emil Dotchevski wrote:
> It doesn't matter what your use case is: while the success/failure
> vocabulary isn't incorrect, it is more precise to think in terms of
> postconditions. You call a function, it will either satisfy its
> postconditions or it won't return. Sure, you can and should return all
> information that the caller needs in order to proceed with whatever
> operation is being attempted, but there is no need to return anything if
> the operation can't proceed.
>
> So all you need to do is correctly identify the conditions that indicate
> that the operation can't proceed, and throw an exception.

That's still a fuzzy line though. In the delete file case, the
operation of deleting the file cannot proceed because the file is
already absent. However the post-condition of "the file no longer
exists" is still met. Is that success or failure?

There are other cases where an operation might fail, or (if you prefer
that term) be unable to proceed (eg. "queue is full, cannot push new
item"), but throwing an exception in this case is not a good design
choice unless you *know* that the queue is never supposed to be full --
which is not something that the queue itself can know unless it's a
supposedly unbounded queue and so the only way it can be full is if it
cannot allocate more memory.

But for bounded queues, the library generally has to assume that it
could reach the bound at some point and have to refuse to add new items,
so that should be a status return rather than an exception. And yet the
application could choose to use such a queue with a really large bound
or with a producer that's massively slower than the consumer, and so the
assumptions the library made about the balance of success vs. failure
weren't correct.



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Emil Dotchevski
2016-02-10 23:23:33 UTC
Permalink
On Wed, Feb 10, 2016 at 1:30 PM, Gavin Lambert <***@compacsort.com>
wrote:

> On 11/02/2016 08:40, Emil Dotchevski wrote:
>
>> It doesn't matter what your use case is: while the success/failure
>> vocabulary isn't incorrect, it is more precise to think in terms of
>> postconditions. You call a function, it will either satisfy its
>> postconditions or it won't return. Sure, you can and should return all
>> information that the caller needs in order to proceed with whatever
>> operation is being attempted, but there is no need to return anything if
>> the operation can't proceed.
>>
>> So all you need to do is correctly identify the conditions that indicate
>> that the operation can't proceed, and throw an exception.
>>
>
> That's still a fuzzy line though. In the delete file case, the operation
> of deleting the file cannot proceed because the file is already absent.
> However the post-condition of "the file no longer exists" is still met. Is
> that success or failure?
>

"Proceed", I mean the caller. Let's say you have code which opens the file
then reads from it. The caller can not proceed to reading if the file
couldn't be opened. So, the postcondition of the open operation is that the
file was successfully opened. Similarly, the postcondition of delete_file
is that the file does not exist, because presumably the caller of
delete_file can't proceed if the file still exists.


> There are other cases where an operation might fail, or (if you prefer
> that term) be unable to proceed (eg. "queue is full, cannot push new
> item"), but throwing an exception in this case is not a good design choice
> unless you *know* that the queue is never supposed to be full -- which is
> not something that the queue itself can know unless it's a supposedly
> unbounded queue and so the only way it can be full is if it cannot allocate
> more memory.
>

If you know that the queue is never supposed to be full, that is, if the
full queue indicates a bug in your code, then you should assert rather than
throw. Throwing is when you expect the program to successfully recover from
an anticipated (by the programmer) failure.


> But for bounded queues, the library generally has to assume that it could
> reach the bound at some point and have to refuse to add new items, so that
> should be a status return rather than an exception.


It depends. For example, if this is a keyboard buffer queue, then probably
you'd return a status to indicate that there's no more space, and the
caller will "beep" to tell the user to slow down with the typing. In this
case it would be annoying for the caller to have to catch an exception. On
the other hand, if it's a job queue, where the caller expects all submitted
jobs to be queued and at some point completed, then an exception is more
appropriate, so the immediate caller wouldn't have to care (push won't
return), while some context higher up the call stack can perhaps retry the
whole sequence of jobs, or notify the user that the operation failed.

We can't discuss this stuff in the abstract. Defining correct
postconditions depends very much on the specific API being designed.


> And yet the application could choose to use such a queue with a really
> large bound or with a producer that's massively slower than the consumer,
> and so the assumptions the library made about the balance of success vs.
> failure weren't correct.


In this case either we have a user error or a library design error. You
have to get the postconditions right, or else the users will end up having
to deal with exceptions in what they consider non-failure situations, or
end up writing (and forgetting to write) if( error ) return error. I
personally have better things to do. :)

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Gavin Lambert
2016-02-11 01:02:26 UTC
Permalink
On 11/02/2016 12:23, Emil Dotchevski wrote:
>> That's still a fuzzy line though. In the delete file case, the operation
>> of deleting the file cannot proceed because the file is already absent.
>> However the post-condition of "the file no longer exists" is still met. Is
>> that success or failure?
>
> "Proceed", I mean the caller. Let's say you have code which opens the file
> then reads from it. The caller can not proceed to reading if the file
> couldn't be opened. So, the postcondition of the open operation is that the
> file was successfully opened. Similarly, the postcondition of delete_file
> is that the file does not exist, because presumably the caller of
> delete_file can't proceed if the file still exists.

As I said before though, there could be some applications (eg. certain
types of databases) which can't proceed if that particular deletion
action was not the cause of the file ceasing to exist (ie. if some other
process deleted it in advance). It's not as clear-cut as you seem to be
suggesting.

> If you know that the queue is never supposed to be full, that is, if the
> full queue indicates a bug in your code, then you should assert rather than
> throw. Throwing is when you expect the program to successfully recover from
> an anticipated (by the programmer) failure.

The library can't know that, though (unless it was designed to be
unbounded, so can't be full unless memory allocation fails), so it must
pass this state out to the application to deal with, and an exception is
*not necessarily* the right way to do so. (And neither is
not-an-exception, for that matter.)

If you *had* to pick one or the other, though, in cases where failure is
unusual (such as failing to push to an unbounded queue) an exception
makes more sense, and in cases where failure is not unusual (such as
failing to push to a bounded queue) a return status makes more sense.

I think it's been well established that exceptions are great for dealing
with unexpected failure (since you have to actively ignore them), but
not so great for expected failure (since they can do heavy things like
capturing call stacks and stack unwinding).

> It depends. For example, if this is a keyboard buffer queue, then probably
> you'd return a status to indicate that there's no more space, and the
> caller will "beep" to tell the user to slow down with the typing. In this
> case it would be annoying for the caller to have to catch an exception. On
> the other hand, if it's a job queue, where the caller expects all submitted
> jobs to be queued and at some point completed, then an exception is more
> appropriate, so the immediate caller wouldn't have to care (push won't
> return), while some context higher up the call stack can perhaps retry the
> whole sequence of jobs, or notify the user that the operation failed.
>
> We can't discuss this stuff in the abstract. Defining correct
> postconditions depends very much on the specific API being designed.

Except that for things like generic data structures, the implementing
library can't possibly know what the application is going to put in them
or do with them.

So you have to deal in the abstract, which is why you often need both
throwing and non-throwing API in those sorts of cases -- only at the
application level can you finally decide which one to call.



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Emil Dotchevski
2016-02-11 01:57:11 UTC
Permalink
On Wed, Feb 10, 2016 at 5:02 PM, Gavin Lambert <***@compacsort.com>
wrote:

> On 11/02/2016 12:23, Emil Dotchevski wrote:
>
>> That's still a fuzzy line though. In the delete file case, the operation
>>> of deleting the file cannot proceed because the file is already absent.
>>> However the post-condition of "the file no longer exists" is still met.
>>> Is
>>> that success or failure?
>>>
>>
>> "Proceed", I mean the caller. Let's say you have code which opens the file
>> then reads from it. The caller can not proceed to reading if the file
>> couldn't be opened. So, the postcondition of the open operation is that
>> the
>> file was successfully opened. Similarly, the postcondition of delete_file
>> is that the file does not exist, because presumably the caller of
>> delete_file can't proceed if the file still exists.
>>
>
> As I said before though, there could be some applications (eg. certain
> types of databases) which can't proceed if that particular deletion action
> was not the cause of the file ceasing to exist (ie. if some other process
> deleted it in advance). It's not as clear-cut as you seem to be suggesting.


I didn't say that all possible functions from all possible libraries that
may delete files should have the same postconditions.


> If you know that the queue is never supposed to be full, that is, if the
>> full queue indicates a bug in your code, then you should assert rather
>> than
>> throw. Throwing is when you expect the program to successfully recover
>> from
>> an anticipated (by the programmer) failure.
>>
>
> The library can't know that, though (unless it was designed to be
> unbounded, so can't be full unless memory allocation fails), so it must
> pass this state out to the application to deal with, and an exception is
> *not necessarily* the right way to do so. (And neither is
> not-an-exception, for that matter.)
>

Here is an example: initializing a shared_ptr from an expired weak_ptr
throws. The designer recognized that this is not always practical, so he
gave us weak_ptr::lock, which does not throw. So the user has both options
available.

On the other hand, you might want dereferencing a shared_ptr to throw an
exception in case it's empty, but that's not an option. Why? Because the
shared_ptr designer said so. Dereferencing an empty shared_ptr is undefined
behavior, and that's that.

Designing correct preconditions and postconditions for each function isn't
easy, and the answer isn't "oh I don't know, you might need 57 different
versions".


> If you *had* to pick one or the other, though, in cases where failure is
> unusual (such as failing to push to an unbounded queue) an exception makes
> more sense, and in cases where failure is not unusual (such as failing to
> push to a bounded queue) a return status makes more sense.
>
> I think it's been well established that exceptions are great for dealing
> with unexpected failure (since you have to actively ignore them), but not
> so great for expected failure (since they can do heavy things like
> capturing call stacks and stack unwinding).
>
> It depends. For example, if this is a keyboard buffer queue, then probably
>> you'd return a status to indicate that there's no more space, and the
>> caller will "beep" to tell the user to slow down with the typing. In this
>> case it would be annoying for the caller to have to catch an exception. On
>> the other hand, if it's a job queue, where the caller expects all
>> submitted
>> jobs to be queued and at some point completed, then an exception is more
>> appropriate, so the immediate caller wouldn't have to care (push won't
>> return), while some context higher up the call stack can perhaps retry the
>> whole sequence of jobs, or notify the user that the operation failed.
>>
>> We can't discuss this stuff in the abstract. Defining correct
>> postconditions depends very much on the specific API being designed.
>>
>
> Except that for things like generic data structures, the implementing
> library can't possibly know what the application is going to put in them or
> do with them.
>
> So you have to deal in the abstract, which is why you often need both
> throwing and non-throwing API in those sorts of cases -- only at the
> application level can you finally decide which one to call.


The shared_ptr functions I just mentioned are counter-examples for the
claim that only the application programmer can make these decisions. If you
need another example, consider std::vector: if you attempt to push_back,
and it needs to grow its buffer, but it fails to grow its buffer, it
throws, even if the value_type has no-throw copy constructor. There is no
no-throw option.

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Gavin Lambert
2016-02-11 01:11:18 UTC
Permalink
On 11/02/2016 12:23, Emil Dotchevski wrote:
> If you know that the queue is never supposed to be full, that is, if the
> full queue indicates a bug in your code, then you should assert rather than
> throw. Throwing is when you expect the program to successfully recover from
> an anticipated (by the programmer) failure.

Sorry, I missed responding to this part.

No, you should not assert in this case. Filling a queue is something
that easily could happen at runtime in release mode (even if the
programmer thinks it's not supposed to), where asserts are disabled and
valueless.

You could assert *and* throw (or abort, if you don't know how to recover
from it), although arguably the assert is less useful if you're
repeating the same condition outside the assert anyway. But you can't
just slap an assert in and call it a day, at least not for this sort of
condition.



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Emil Dotchevski
2016-02-11 02:08:23 UTC
Permalink
On Wed, Feb 10, 2016 at 5:11 PM, Gavin Lambert <***@compacsort.com>
wrote:

> On 11/02/2016 12:23, Emil Dotchevski wrote:
>
>> If you know that the queue is never supposed to be full, that is, if the
>> full queue indicates a bug in your code, then you should assert rather
>> than
>> throw. Throwing is when you expect the program to successfully recover
>> from
>> an anticipated (by the programmer) failure.
>>
>
> Sorry, I missed responding to this part.
>
> No, you should not assert in this case. Filling a queue is something that
> easily could happen at runtime in release mode (even if the programmer
> thinks it's not supposed to), where asserts are disabled and valueless.
>

Dereferencing a null shared_ptr is something that easily could happen at
runtime. Are you saying that it shouldn't assert in this case? Are you
saying that that assert is useless? Are you saying that the correct design
is to throw? Or is the correct design to assert and throw?

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Gavin Lambert
2016-02-11 05:23:10 UTC
Permalink
On 11/02/2016 15:08, Emil Dotchevski wrote:
> Dereferencing a null shared_ptr is something that easily could happen at
> runtime. Are you saying that it shouldn't assert in this case? Are you
> saying that that assert is useless? Are you saying that the correct design
> is to throw? Or is the correct design to assert and throw?

I answered that in the next paragraph:

On 11/02/2016 14:11, I wrote:
> You could assert *and* throw (or abort, if you don't know how to
> recover from it), although arguably the assert is less useful if
> you're repeating the same condition outside the assert anyway. But
> you can't just slap an assert in and call it a day, at least not for
> this sort of condition.

Or you could just throw. I'm not convinced that the assert itself adds
any particular value, aside from possibly being more in-your-face when
running a debug build. (But thrown exceptions can be equally in your
face in both debug and release builds if you have a debugger attached,
and if throwing exceptions is unusual so you haven't disabled "break on
exception throw".)



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Emil Dotchevski
2016-02-11 22:57:49 UTC
Permalink
On Wed, Feb 10, 2016 at 9:23 PM, Gavin Lambert <***@compacsort.com>
wrote:

> On 11/02/2016 15:08, Emil Dotchevski wrote:
>
>> Dereferencing a null shared_ptr is something that easily could happen at
>> runtime. Are you saying that it shouldn't assert in this case? Are you
>> saying that that assert is useless? Are you saying that the correct design
>> is to throw? Or is the correct design to assert and throw?
>>
>
> I answered that in the next paragraph:
>
> On 11/02/2016 14:11, I wrote:
>
>> You could assert *and* throw (or abort, if you don't know how to
>> recover from it), although arguably the assert is less useful if
>> you're repeating the same condition outside the assert anyway. But
>> you can't just slap an assert in and call it a day, at least not for
>> this sort of condition.
>>
>
> Or you could just throw. I'm not convinced that the assert itself adds
> any particular value, aside from possibly being more in-your-face when
> running a debug build. (But thrown exceptions can be equally in your face
> in both debug and release builds if you have a debugger attached, and if
> throwing exceptions is unusual so you haven't disabled "break on exception
> throw".)


It appears that you think that C++ exceptions are "unusual" in the same way
OS exceptions are unusual: the OS detected something bad going on and
raises an exception, as many OSes do for example in the case of
dereferencing a null pointer. That's not at all what C++ exceptions are.
They are in fact specifically designed to replace the need to return error
codes, so that handling errors is simpler, safer and more testable.

As for shared_ptr::operator*, I know it could be made to throw, but that
would be incorrect design. I mentioned shared_ptr to make the point that
you're wrong that this kind of design decision can not be made in generic
C++ code. STL too is full of generic functions that throw to indicate a
failure, and others that do not, without giving the user a choice. Even at
the language level, consider that in C++ constructors don't give you the
option to return an error code, the only way for them to fail is by
throwing. Why? Because that is the correct design.

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Gavin Lambert
2016-02-11 23:32:00 UTC
Permalink
On 12/02/2016 11:57, Emil Dotchevski wrote:
> It appears that you think that C++ exceptions are "unusual" in the same way
> OS exceptions are unusual: the OS detected something bad going on and
> raises an exception, as many OSes do for example in the case of
> dereferencing a null pointer. That's not at all what C++ exceptions are.
> They are in fact specifically designed to replace the need to return error
> codes, so that handling errors is simpler, safer and more testable.

Perhaps I'm biased by mostly developing on Windows+MSVC, but since that
implements C++ exceptions by raising OS exceptions, and OS exceptions
can be caught and processed just like C++ exceptions, I don't see any
distinction between the two. (Though I'm aware that the OS exception
handling mechanism is much more broken on non-Windows.)

And yes, I think that exceptions should be reserved for unexpected
cases. If a method has a case where it is expected to sometimes not
produce a value, then it should use optional<T> or similar.

> As for shared_ptr::operator*, I know it could be made to throw, but that
> would be incorrect design. I mentioned shared_ptr to make the point that
> you're wrong that this kind of design decision can not be made in generic
> C++ code. STL too is full of generic functions that throw to indicate a
> failure, and others that do not, without giving the user a choice. Even at
> the language level, consider that in C++ constructors don't give you the
> option to return an error code, the only way for them to fail is by
> throwing. Why? Because that is the correct design.

The only reason that shared_ptr::operator* does not throw is that the
class author decided that this is likely a hot path and the calling code
has *probably* already checked for null, so it is more *efficient* to
omit the check entirely (and cause undefined behavior if called in
violation of that assumption). As such an assert is used to *hopefully*
catch those violations, but there are only limited cases in which it
will do so. Checking and throwing will always be more *safe/correct*,
but for this particular method the author chose to prioritise efficiency
over safety.

(Although shared_ptr has an additional bias -- if the operator*
precondition is violated then even if the assert is omitted it's almost
certainly going to immediately cause an OS fault anyway due to a null
(or nearly-null) pointer access -- which is arguably equivalent to
always-throw behaviour. Methods on other classes typically won't get
that for free.)

Don't get me wrong, I'm not saying that this is a bad thing. But it can
also get applications into trouble and it is useful to provide both
options. Just look at all the discussion recently about safe numeric
libraries -- that's another case where the language has chosen to value
efficiency over safety, and some applications wish for the reverse.



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Emil Dotchevski
2016-02-11 23:56:24 UTC
Permalink
On Thu, Feb 11, 2016 at 3:32 PM, Gavin Lambert <***@compacsort.com>
wrote:

> On 12/02/2016 11:57, Emil Dotchevski wrote:
>
>> It appears that you think that C++ exceptions are "unusual" in the same
>> way
>> OS exceptions are unusual: the OS detected something bad going on and
>> raises an exception, as many OSes do for example in the case of
>> dereferencing a null pointer. That's not at all what C++ exceptions are.
>> They are in fact specifically designed to replace the need to return error
>> codes, so that handling errors is simpler, safer and more testable.
>>
>
> Perhaps I'm biased by mostly developing on Windows+MSVC, but since that
> implements C++ exceptions by raising OS exceptions, and OS exceptions can
> be caught and processed just like C++ exceptions, I don't see any
> distinction between the two. (Though I'm aware that the OS exception
> handling mechanism is much more broken on non-Windows.)
>

It's criminal that MSVC can translate OS exceptions into C++ exceptions. :)
It's not that it's broken on other OSes, other compilers don't do this
because it's wrong. I do not recommend turning that MSVC option on.


> And yes, I think that exceptions should be reserved for unexpected cases.
> If a method has a case where it is expected to sometimes not produce a
> value, then it should use optional<T> or similar.
>

I can see that from your responses but you're wrong. In C++, you can not
get an exception unexpectedly. The only way to throw an exception in C++ is
to use the throw keyword. Contrast this with hardware exceptions, which may
be raised by virtually any CPU instruction and have nothing to do with C++
exceptions.


> As for shared_ptr::operator*, I know it could be made to throw, but that
>> would be incorrect design. I mentioned shared_ptr to make the point that
>> you're wrong that this kind of design decision can not be made in generic
>> C++ code. STL too is full of generic functions that throw to indicate a
>> failure, and others that do not, without giving the user a choice. Even at
>> the language level, consider that in C++ constructors don't give you the
>> option to return an error code, the only way for them to fail is by
>> throwing. Why? Because that is the correct design.
>>
>
> The only reason that shared_ptr::operator* does not throw is that the
> class author decided that this is likely a hot path and the calling code
> has *probably* already checked for null, so it is more *efficient* to omit
> the check entirely (and cause undefined behavior if called in violation of
> that assumption).


He can speak for himself, but I bet that his motivation was to avoid
overhead in the extremely common use case when the programmer *knows* that
the shared_ptr isn't null. If I have a shared_ptr (that I didn't get from
weak_ptr::lock), more often than not it would be a logic error for it to be
null, so it would be dumb to check if it is null when dereferencing it.


> (Although shared_ptr has an additional bias -- if the operator*
> precondition is violated then even if the assert is omitted it's almost
> certainly going to immediately cause an OS fault anyway due to a null (or
> nearly-null) pointer access -- which is arguably equivalent to always-throw
> behaviour.


Absolutely not. Dereferencing a null pointer in C++ is undefined behavior,
which is not at all the same as throwing exceptions or raising OS
exceptions. When you throw an exception, the standard specifies exactly
what is going to happen, but in the case of undefined behavior all bets are
off. Maybe your program will crash, maybe it'll send a nasty email to your
boss. :)

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Gavin Lambert
2016-02-12 00:57:26 UTC
Permalink
On 12/02/2016 12:56, Emil Dotchevski wrote:
>> Perhaps I'm biased by mostly developing on Windows+MSVC, but since that
>> implements C++ exceptions by raising OS exceptions, and OS exceptions can
>> be caught and processed just like C++ exceptions, I don't see any
>> distinction between the two. (Though I'm aware that the OS exception
>> handling mechanism is much more broken on non-Windows.)
>
> It's criminal that MSVC can translate OS exceptions into C++ exceptions. :)
> It's not that it's broken on other OSes, other compilers don't do this
> because it's wrong. I do not recommend turning that MSVC option on.

It's not an option. That's just what it does. There are some good
things about this, as it makes it easier to recover a stack trace from a
thrown exception -- though the corollary bad thing is that exceptions
are also more expensive to throw.

(Although there *is* an option which decides how aggressively it inserts
the plumbing to catch OS exceptions, and leaving that off can improve
performance at the cost of reducing the places that you can catch them.)

>> The only reason that shared_ptr::operator* does not throw is that the
>> class author decided that this is likely a hot path and the calling code
>> has *probably* already checked for null, so it is more *efficient* to omit
>> the check entirely (and cause undefined behavior if called in violation of
>> that assumption).
>
> He can speak for himself, but I bet that his motivation was to avoid
> overhead in the extremely common use case when the programmer *knows* that
> the shared_ptr isn't null. If I have a shared_ptr (that I didn't get from
> weak_ptr::lock), more often than not it would be a logic error for it to be
> null, so it would be dumb to check if it is null when dereferencing it.

Which is exactly what I said.

> Absolutely not. Dereferencing a null pointer in C++ is undefined behavior,
> which is not at all the same as throwing exceptions or raising OS
> exceptions. When you throw an exception, the standard specifies exactly
> what is going to happen, but in the case of undefined behavior all bets are
> off. Maybe your program will crash, maybe it'll send a nasty email to your
> boss. :)

On pretty much any platform with an MPU the memory around address 0 is
left unmapped precisely such that such accesses generate an OS fault.
Yes, this is technically undefined behaviour at the language level, but
it is not undefined behaviour at the platform level (though it may still
be "defined but unintended behaviour").

And yes, platforms do exist that do not have that characteristic, and
they do have C++ compilers, and in that environment you won't get a
fault in that case, you will get defined-but-almost-certainly-incorrect
behaviour. So truly portable applications cannot *rely* on getting a
fault, but there is a decent chance that they will experience it at some
point nevertheless.

You're taking this out of context, though; it was an aside only
peripherally related to the main point.



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Emil Dotchevski
2016-02-12 01:27:20 UTC
Permalink
On Thu, Feb 11, 2016 at 4:57 PM, Gavin Lambert <***@compacsort.com>
wrote:

> On 12/02/2016 12:56, Emil Dotchevski wrote:
>
>> Perhaps I'm biased by mostly developing on Windows+MSVC, but since that
>>> implements C++ exceptions by raising OS exceptions, and OS exceptions can
>>> be caught and processed just like C++ exceptions, I don't see any
>>> distinction between the two. (Though I'm aware that the OS exception
>>> handling mechanism is much more broken on non-Windows.)
>>>
>>
>> It's criminal that MSVC can translate OS exceptions into C++ exceptions.
>> :)
>> It's not that it's broken on other OSes, other compilers don't do this
>> because it's wrong. I do not recommend turning that MSVC option on.
>>
>
> It's not an option. That's just what it does.


Not true, it's an option. They call it "structural exception handling".


> There are some good things about this, as it makes it easier to recover a
> stack trace from a thrown exception -- though the corollary bad thing is
> that exceptions are also more expensive to throw.
>

You can love it or hate it, but as a matter of fact it's not standard
behavior, and I am not aware of any compiler other than MSVC that
implements it. Basically, long time ago someone at Microsoft read the word
"exception" and interpreted the standard incorrectly, and now they can't
remove this broken behavior for legacy reasons.


> Absolutely not. Dereferencing a null pointer in C++ is undefined behavior,
>> which is not at all the same as throwing exceptions or raising OS
>> exceptions. When you throw an exception, the standard specifies exactly
>> what is going to happen, but in the case of undefined behavior all bets
>> are
>> off. Maybe your program will crash, maybe it'll send a nasty email to your
>> boss. :)
>>
>
> On pretty much any platform with an MPU the memory around address 0 is
> left unmapped precisely such that such accesses generate an OS fault.


Yes, but there is no requirement for the program to be able to unwind the
stack and call destructors, as it does when throwing C++ exceptions. It's
makes no sense to expect a program to be able to recover from undefined
behavior because at that point the state of the program is undefined, by
definition.


> You're taking this out of context, though; it was an aside only
> peripherally related to the main point.


If we're talking about the problems of C++ exception handling and when it
is and it isn't appropriate to use it, it's important to be on the same
page about what exception handling is. And it most definitely is NOT the
"structural exception handling" behavior in MSVC. That should never be
used, except if you're dealing with an old code base that relies on it.

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Gavin Lambert
2016-02-12 02:36:34 UTC
Permalink
On 12/02/2016 14:27, Emil Dotchevski wrote:
>>> It's criminal that MSVC can translate OS exceptions into C++ exceptions.
>>> :)
>>> It's not that it's broken on other OSes, other compilers don't do this
>>> because it's wrong. I do not recommend turning that MSVC option on.
>>>
>> It's not an option. That's just what it does.
>
> Not true, it's an option. They call it "structural exception handling".

As I mentioned, as far as I am aware that just disables the ability to
catch such exceptions (in part, by only inserting the logic for
unwinding in places that it expects C++ exceptions). I don't think it
stops the compiler transporting exceptions that way. I could be wrong
about that though.

>> On pretty much any platform with an MPU the memory around address 0 is
>> left unmapped precisely such that such accesses generate an OS fault.
>
> Yes, but there is no requirement for the program to be able to unwind the
> stack and call destructors, as it does when throwing C++ exceptions. It's
> makes no sense to expect a program to be able to recover from undefined
> behavior because at that point the state of the program is undefined, by
> definition.

Except it's not. If a particular platform generates a bus error on
access to protected memory (including null memory), it typically further
guarantees how to recover from such a hardware exception (typically by
adjusting the return address), and indeed all OSes do so in some fashion
or another.

This is not really any different from platforms like Java or .NET that
throw a NullReferenceException or other AccessViolationException or
whatever in this case. (Except that those languages typically make it
slightly harder to stomp completely roughshod over memory.)

On Windows that translates to a structured exception which is
thread-specific and (if async exceptions are enabled) can be caught,
logged, and recovered from in some sane fashion *if* the application can
know what is sane -- but especially in state-free applications (eg. web
servers) that is also well defined, usually by abandoning the operation
and logging or returning an error.

On POSIX that translates to a signal that is not thread-specific and
can't really be dealt with in any sane way except terminating the entire
process.

While terminating the process is *often* the best reaction to this sort
of failure, it is not exclusively so, and POSIX is dumb for not
permitting the application the choice. (Admittedly Windows is also dumb
for permitting the application the choice, since there are a lot of
people who do completely the wrong thing. You can't really argue that
either way is the best.)

Again, though, this is a platform-level definition and not a
language-level definition. C++ itself does not define what happens when
you access memory out of bounds -- the platform does, and if you're
writing portable code that's platform-agnostic then you cannot rely on
the behaviour of a particular platform, obviously. But if you aren't,
then you can.



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Emil Dotchevski
2016-02-12 23:56:38 UTC
Permalink
On Thu, Feb 11, 2016 at 6:36 PM, Gavin Lambert <***@compacsort.com>
wrote:

> On 12/02/2016 14:27, Emil Dotchevski wrote:
>
>> It's criminal that MSVC can translate OS exceptions into C++ exceptions.
>>>> :)
>>>> It's not that it's broken on other OSes, other compilers don't do this
>>>> because it's wrong. I do not recommend turning that MSVC option on.
>>>>
>>>> It's not an option. That's just what it does.
>>>
>>
>> Not true, it's an option. They call it "structural exception handling".
>>
>
> As I mentioned, as far as I am aware that just disables the ability to
> catch such exceptions (in part, by only inserting the logic for unwinding
> in places that it expects C++ exceptions). I don't think it stops the
> compiler transporting exceptions that way. I could be wrong about that
> though.
>

The __try/__except and __try/__finally statements are C (not C++)
extensions of MSVC. See
https://msdn.microsoft.com/en-us/library/swezty51.aspx.

Optionally the C++ compiler can generate code that treats these exceptions
as C++ exceptions. Microsoft does not recommend using that option, as
indicated by the first paragraph from the linked article:

"Although Windows and Visual C++ support structured exception handling
(SEH), we recommend that you use ISO-standard C++ exception handling
because it makes code more portable and flexible."

So, don't enable this behavior, it leads to too much overhead and has
nothing to do with how C++ exceptions are supposed to work.

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas
2016-02-12 11:20:34 UTC
Permalink
On 11 Feb 2016 at 17:27, Emil Dotchevski wrote:

> You can love it or hate it, but as a matter of fact it's not standard
> behavior, and I am not aware of any compiler other than MSVC that
> implements it. Basically, long time ago someone at Microsoft read the word
> "exception" and interpreted the standard incorrectly, and now they can't
> remove this broken behavior for legacy reasons.

Win32 structured exception handling has nothing to do with C++, it
was how the NT kernel implementors decided to fix the extremely
broken POSIX signals design. And it is a *superb* solution to the
problem of handling unexpected failure at the system level, I really
wish POSIX would mark signals entirely as deprecated and adopt pretty
much exactly structured exception handling as the POSIX standard.

LLVM, for the record, has implemented a very similar "universal"
exception handling mechanism which allows exception throws to
traverse language boundaries within LLVM, and my great hope is that
once LLVM becomes universal on POSIX platforms someone will go ahead
and replace signals with structured exception handling. Microsoft
simply got to the same point much earlier with less practical
experience and much worse tooling, and made some decision designs
which with the benefit of hindsight look poor, but at the time a
universal exception framework looked as valuable to Microsoft as it
does today to LLVM.

Microsoft's C++ implementation has a ton of warts, but you've got to
remember it is the oldest C++ implementation in the world. They are
*still* ABI compatible with the earliest 32 bit binaries on DOS. The
fact it works as well as it does for modern C++ is an astounding
engineering achievement.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/
http://ie.linkedin.com/in/nialldouglas/
Peter Dimov
2016-02-12 00:02:45 UTC
Permalink
Gavin Lambert wrote:
> The only reason that shared_ptr::operator* does not throw is that the
> class author decided that this is likely a hot path and the calling code
> has *probably* already checked for null, so it is more *efficient* to omit
> the check entirely (and cause undefined behavior if called in violation of
> that assumption).

That's not true. The class author decided that calling operator* on a NULL
pointer is a logic error and therefore a correct program should never do so.
This doesn't have anything to do with performance; it's a question of
design.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Gavin Lambert
2016-02-12 00:40:18 UTC
Permalink
On 12/02/2016 13:02, Peter Dimov wrote:
> Gavin Lambert wrote:
>> The only reason that shared_ptr::operator* does not throw is that the
>> class author decided that this is likely a hot path and the calling
>> code has *probably* already checked for null, so it is more
>> *efficient* to omit the check entirely (and cause undefined behavior
>> if called in violation of that assumption).
>
> That's not true. The class author decided that calling operator* on a
> NULL pointer is a logic error and therefore a correct program should
> never do so. This doesn't have anything to do with performance; it's a
> question of design.

Then why doesn't it throw a logic_error? Or call abort()?

Again, an assert is a useful tool in debug mode but it disappears in
release mode. The only reason to elide this check is for performance.

Much as it might be considered "bad practice", there *is* software that
is only ever built and tested in release mode (or at least only rarely
in debug mode). For this software, the assert might as well not be
there -- because it isn't.



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Peter Dimov
2016-02-12 00:50:48 UTC
Permalink
Gavin Lambert wrote:
> On 12/02/2016 13:02, Peter Dimov wrote:
> > That's not true. The class author decided that calling operator* on a
> > NULL pointer is a logic error and therefore a correct program should
> > never do so. This doesn't have anything to do with performance; it's a
> > question of design.
>
> Then why doesn't it throw a logic_error?

Because throwing a logic_error is an oxymoron. Logic errors should not
throw.

> Or call abort()?

That's basically what it does. Or a crash when asserts are disabled.

> Again, an assert is a useful tool in debug mode but it disappears in
> release mode. The only reason to elide this check is for performance.

That's correct.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Gavin Lambert
2016-02-12 01:01:34 UTC
Permalink
On 12/02/2016 13:50, Peter Dimov wrote:
>> Then why doesn't it throw a logic_error?
>
> Because throwing a logic_error is an oxymoron. Logic errors should not
> throw.

And yet it was defined in the standard library for that purpose. :)

>> Or call abort()?
>
> That's basically what it does. Or a crash when asserts are disabled.
>
>> Again, an assert is a useful tool in debug mode but it disappears in
>> release mode. The only reason to elide this check is for performance.
>
> That's correct.

Then it appears we are in violent agreement after all. :)



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Peter Dimov
2016-02-12 01:26:29 UTC
Permalink
Gavin Lambert wrote:
> >> Again, an assert is a useful tool in debug mode but it disappears in
> >> release mode. The only reason to elide this check is for performance.
> >
> > That's correct.
>
> Then it appears we are in violent agreement after all. :)

No, we aren't. Performance is a legitimate reason to elide the check, but
it's not the reason that op* doesn't throw (by contract). Again, throwing on
a logic error is an oxymoron. If the function is specified to throw under
such-and-such conditions, it's not an error to call it under those
conditions.

All that seems to deviate a bit from the actual topic of the thread though.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Bjorn Reese
2016-02-11 23:17:46 UTC
Permalink
On 02/11/2016 11:57 PM, Emil Dotchevski wrote:

> failure, and others that do not, without giving the user a choice. Even at
> the language level, consider that in C++ constructors don't give you the
> option to return an error code, the only way for them to fail is by
> throwing. Why? Because that is the correct design.

True. However, there are situations where throwing exceptions is the
wrong design, like continuations (e.g. future<T>::then) or asynchronous
return values (e.g. Boost.Asio handlers.) For both the situations a
value-or-error type, like excepted<T>, is a correct design.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Emil Dotchevski
2016-02-11 23:33:04 UTC
Permalink
On Thu, Feb 11, 2016 at 3:17 PM, Bjorn Reese <***@mail1.stofanet.dk>
wrote:

> On 02/11/2016 11:57 PM, Emil Dotchevski wrote:
>
> failure, and others that do not, without giving the user a choice. Even at
>> the language level, consider that in C++ constructors don't give you the
>> option to return an error code, the only way for them to fail is by
>> throwing. Why? Because that is the correct design.
>>
>
> True. However, there are situations where throwing exceptions is the
> wrong design, like continuations (e.g. future<T>::then) or asynchronous
> return values (e.g. Boost.Asio handlers.) For both the situations a
> value-or-error type, like excepted<T>, is a correct design.


Obviously there are such situations when throwing is the wrong design. In
C++ there are three options in case of errors:

1) undefined behavior (e.g. assert)
2) report the error to the caller
3) throw

Choosing the correct behavior(s) is not always trivial, but "oh, I'll let
the user choose what's best for him" is not good design.

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas
2016-02-10 18:57:54 UTC
Permalink
On 9 Feb 2016 at 12:04, Emil Dotchevski wrote:

> It feels strange to have to defend the use of exceptions for reporting
> errors in C++, on the boost development board of all places. There are many
> other advantages, for example when returning errors there is no such thing
> as error-neutral contexts in your program, which increases coupling. Yes,
> in some contexts one can't afford to use exceptions, but all general
> complains that exception handling causes performance or any other problems
> are theoretical, at best.

I've noticed a lot of people taking issue with the overhead of
exceptions really mean to say they take issue with the
*indeterminacy* introduced by exceptions, and even that often is
really a proxy for the phrase "indirect/implicit/hidden/non-obvious
use of malloc() or free()" which is the main source of unpredictable
exception throws.

In other words, people don't mind predictable exceptions anything
like as much as potential unpredictable unknowable overheads.

My current contract has my coworkers highly surprised that fixed
worst case latency code can be easily written using the STL. They had
assumed that games and audio development banned use of the STL and
exceptions due to unpredictable execution times. They are not wrong,
you just need to learn off which bits of the STL could call malloc or
have worse than linear execution times and which bits never will, and
only use the latter in hot code paths. That's really a
training/familiarity(/maintenance) problem in the end.

> > With just a little extra libclang tooling (some
> > of which I plan to write) this style idiom ought to be mathematically
> > provable as correct in the functional programming sense, which would
> > be cool, not least for those programming nuclear reactors etc.
>
> Could you prove anything mathematically in the presence of side effects and
> pointers?

It's not my field so everything I'm about to say next is hearsay, but
back during the nuclear reactors certification for QNX (which is
written in C) I noticed you must always assume that functions you
call behave as specified and the only goal is to prove the current
function you are proving is no worse than the things it calls. From
what I saw, you can't prove a program, but you can prove a program if
you assume everything it calls is correct and you don't do a long
list of things in C which would break the proof. They had LLVM based
tooling which generated the proofs from the AST or flagged code where
you were doing something not permitted, it appeared to work very
well.

Obviously C++ is orders of magnitude harder, but with a restrictive
enough list of things you can't do I'm sure it's achievable. Whether
such a program would still qualify as C++ is an open question.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/
http://ie.linkedin.com/in/nialldouglas/
Emil Dotchevski
2016-02-10 20:26:51 UTC
Permalink
On Wed, Feb 10, 2016 at 10:57 AM, Niall Douglas <***@nedprod.com>
wrote:

> On 9 Feb 2016 at 12:04, Emil Dotchevski wrote:
>
> > It feels strange to have to defend the use of exceptions for reporting
> > errors in C++, on the boost development board of all places. There are
> many
> > other advantages, for example when returning errors there is no such
> thing
> > as error-neutral contexts in your program, which increases coupling. Yes,
> > in some contexts one can't afford to use exceptions, but all general
> > complains that exception handling causes performance or any other
> problems
> > are theoretical, at best.
>
> I've noticed a lot of people taking issue with the overhead of
> exceptions really mean to say they take issue with the
> *indeterminacy* introduced by exceptions, and even that often is
> really a proxy for the phrase "indirect/implicit/hidden/non-obvious
> use of malloc() or free()" which is the main source of unpredictable
> exception throws.
>
> In other words, people don't mind predictable exceptions anything
> like as much as potential unpredictable unknowable overheads.
>
> My current contract has my coworkers highly surprised that fixed
> worst case latency code can be easily written using the STL. They had
> assumed that games and audio development banned use of the STL and
> exceptions due to unpredictable execution times. They are not wrong,
> you just need to learn off which bits of the STL could call malloc or
> have worse than linear execution times and which bits never will, and
> only use the latter in hot code paths. That's really a
> training/familiarity(/maintenance) problem in the end.
>

If such tricky code targets multiple platforms, you should assume that any
standard C or C++ or OS function may allocate memory. Keep in mind that the
standard guarantees overall complexity, rather than the performance of a
specific call. For example, while std::sort is generally O(n log n), it may
allocate memory, and while the speed of that specific allocation is
independent of the number of elements you're sorting, it might be O(n^2)
for some other (possibly much larger) n.

I'd argue that in such tricky use cases you should be more concerned about
asynchronous events like memory allocations and unpredictable OS hitches
than about exception handling overhead. It's true that in some specific
case throwing an exception may be "too slow", but it'd be too slow as a
matter of fact (because the profiler said so) not because of some general
reasoning; and the solution is to not throw in that case, rather than to
avoid exception handling in principle.


> > > With just a little extra libclang tooling (some
> > > of which I plan to write) this style idiom ought to be mathematically
> > > provable as correct in the functional programming sense, which would
> > > be cool, not least for those programming nuclear reactors etc.
> >
> > Could you prove anything mathematically in the presence of side effects
> and
> > pointers?
>
> It's not my field so everything I'm about to say next is hearsay, but
> back during the nuclear reactors certification for QNX (which is
> written in C) I noticed you must always assume that functions you
> call behave as specified and the only goal is to prove the current
> function you are proving is no worse than the things it calls. From
> what I saw, you can't prove a program, but you can prove a program if
> you assume everything it calls is correct and you don't do a long
> list of things in C which would break the proof. They had LLVM based
> tooling which generated the proofs from the AST or flagged code where
> you were doing something not permitted, it appeared to work very
> well.
>
> Obviously C++ is orders of magnitude harder, but with a restrictive
> enough list of things you can't do I'm sure it's achievable. Whether
> such a program would still qualify as C++ is an open question.
>

I'm told that to this day some programs written in Fortran outperform
equivalent C programs, and that's because in the presence of pointers and
side effects it is impossible for the C optimizer to find all possible ways
a piece of memory can change. So, you can reason all you want about errors,
but if your program is in C, you must test; and testing the error path of
the code is extremely tricky. If you don't use exceptions, you're admitting
the possibility for logic errors every time you call a function that may
fail.

Logically, the only thing exception handling does is automatically check
for failures every time you call a function. It's a way to get the compiler
to automatically write if( error ) return error for you. This is a Good
Thing.

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Michael Marcin
2016-02-11 07:19:04 UTC
Permalink
On 2/9/2016 3:16 AM, Niall Douglas wrote:
>
> Something missing from the discussion so far is that
> expected/result/outcome MUST throw exceptions! If you try to fetch a
> value from a result and it contains an error, there is no alternative
> to throwing an exception. This fact is why I don't worry about static
> function initialisers and just go ahead and use
> error-code-via-system_error throwing constructors, ultimately you
> need try...catch in there one way or another.
>

There most certainly is an alternative!
I certainly don't want this behavior.

There's no way that accessing the value of a result<T> without checking
for an error is not a programming error.
It should be a precondition that to access the value it needs to not
contain an error.
It should *not* be throwing an exception because you cannot fail to
satisfy the postcondition until you meet the preconditions.

Undefined-behavior is the appropriate specification for accessing a
value from a result that contains an error.




_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas
2016-02-11 11:47:25 UTC
Permalink
On 11 Feb 2016 at 1:19, Michael Marcin wrote:

> > Something missing from the discussion so far is that
> > expected/result/outcome MUST throw exceptions! If you try to fetch a
> > value from a result and it contains an error, there is no alternative
> > to throwing an exception. This fact is why I don't worry about static
> > function initialisers and just go ahead and use
> > error-code-via-system_error throwing constructors, ultimately you
> > need try...catch in there one way or another.
> >
>
> There most certainly is an alternative!
> I certainly don't want this behavior.
>
> There's no way that accessing the value of a result<T> without checking
> for an error is not a programming error.
> It should be a precondition that to access the value it needs to not
> contain an error.
> It should *not* be throwing an exception because you cannot fail to
> satisfy the postcondition until you meet the preconditions.
>
> Undefined-behavior is the appropriate specification for accessing a
> value from a result that contains an error.

This is easy to say, but I wouldn't be happy with simply documenting
"all bets are off if you do this" because it's very easy to do
accidentally. Really I want some method of trapping it at compile
time.

The reason Outcome currently throws an exception when trying to get()
from an errored/excepted outcome is because I don't know how to get
it to refuse to compile instead, and the next best alternative of
refusing to link is extremely non-descriptive (specifically it tells
you absolutely nothing about where in your code the unchecked get()
is being performed). Rather than implement something distinctly
unhelpful I've left it as is for now.

However rest assured Outcome will eventually work as described with
RTTI and exceptions off just as AFIO v2 will. Right now it doesn't,
but it's my first todo item after the ACCU conference in April and
Outcome was deliberately designed to work perfectly with RTTI and
exceptions off, it just currently fails due to minor unfinished
problems like the above mentioned as I felt I need a lot more
reflection time on what to do to fix them properly.

And between now and then, simply always check your return before
get() and the compiler will elide any potential exception throw e.g.

outcome<int> _foo; // contains some value
int foo;
if(_foo) foo=_foo.get();
foo=_foo.get_or(5); // also works

If compiled with optimisation you should see no exception throwing
code emitted.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/
http://ie.linkedin.com/in/nialldouglas/
Michael Marcin
2016-02-11 17:57:13 UTC
Permalink
On 2/11/2016 5:47 AM, Niall Douglas wrote:
> outcome<int> _foo; // contains some value
> int foo;
> if(_foo) foo=_foo.get();
> foo=_foo.get_or(5); // also works

I'm not convinced.

I ran a comparison of a trivial implementation of result with your test
code versus your implementation.
The results weren't pretty.

Here's my test code:
http://ideone.com/JYsSYc

Here's the results (VC2015 /O2 /Ob2)

Trivial:

int main()
{
00007FF7B7053530 sub rsp,48h
00007FF7B7053534 mov rax,qword ptr [__security_cookie
(07FF7B705D020h)]
00007FF7B705353B xor rax,rsp
00007FF7B705353E mov qword ptr [rsp+38h],rax
result<int> _foo = bar();
00007FF7B7053543 lea rcx,[_foo]
00007FF7B7053548 call bar (07FF7B7051172h)
int foo = 0;
00007FF7B705354D xor eax,eax
if ( _foo ) {
00007FF7B705354F lea rcx,[instance (07FF7B705D000h)]
00007FF7B7053556 cmp qword ptr [rsp+30h],rcx
00007FF7B705355B cmove eax,dword ptr [_foo]
foo = _foo.get();
}
return foo;
}
00007FF7B7053560 mov rcx,qword ptr [rsp+38h]
00007FF7B7053565 xor rcx,rsp
00007FF7B7053568 call __security_check_cookie (07FF7B70512A3h)
00007FF7B705356D add rsp,48h
00007FF7B7053571 ret


Boost.Outcome

int main()
{
00007FF6E25C2BB0 mov rax,rsp
00007FF6E25C2BB3 push rbp
00007FF6E25C2BB4 lea rbp,[rax-58h]
00007FF6E25C2BB8 sub rsp,150h
00007FF6E25C2BBF mov qword ptr [rbp-38h],0FFFFFFFFFFFFFFFEh
00007FF6E25C2BC7 mov qword ptr [rax+8],rbx
00007FF6E25C2BCB movaps xmmword ptr [rax-18h],xmm6
00007FF6E25C2BCF mov rax,qword ptr [__security_cookie
(07FF6E25CC010h)]
00007FF6E25C2BD6 xor rax,rsp
00007FF6E25C2BD9 mov qword ptr [rbp+30h],rax
result<int> _foo = bar();
00007FF6E25C2BDD lea rcx,[rbp-50h]
00007FF6E25C2BE1 call bar (07FF6E25C109Bh)
00007FF6E25C2BE6 nop
int foo = 0;
00007FF6E25C2BE7 xor ebx,ebx
if ( _foo ) {
00007FF6E25C2BE9 movzx ecx,byte ptr [rbp-40h]
00007FF6E25C2BED cmp cl,1
00007FF6E25C2BF0 sete al
00007FF6E25C2BF3 test al,al
00007FF6E25C2BF5 je main+267h (07FF6E25C2E17h)
foo = _foo.get();
00007FF6E25C2BFB test cl,cl
00007FF6E25C2BFD setne al
00007FF6E25C2C00 test al,al
00007FF6E25C2C02 jne main+119h (07FF6E25C2CC9h)
00007FF6E25C2C08 mov dword ptr [rsp+20h],2
00007FF6E25C2C10 call boost::outcome::v1_std_std::monad_category
(07FF6E25C10DCh)
00007FF6E25C2C15 mov qword ptr [rsp+28h],rax
00007FF6E25C2C1A movaps xmm0,xmmword ptr [rsp+20h]
00007FF6E25C2C1F movdqa xmmword ptr [rsp+20h],xmm0
00007FF6E25C2C25 lea rdx,[rbp-10h]
00007FF6E25C2C29 lea rcx,[rsp+20h]
foo = _foo.get();
00007FF6E25C2C2E call std::error_code::message (07FF6E25C1190h)
00007FF6E25C2C33 cmp qword ptr [rax+18h],10h
00007FF6E25C2C38 jb main+8Dh (07FF6E25C2C3Dh)
00007FF6E25C2C3A mov rax,qword ptr [rax]
00007FF6E25C2C3D lea rcx,[std::exception::`vftable'
(07FF6E25C9D68h)]
00007FF6E25C2C44 mov qword ptr [rsp+58h],rcx
00007FF6E25C2C49 xor ecx,ecx
00007FF6E25C2C4B mov qword ptr [rsp+60h],rcx
00007FF6E25C2C50 mov qword ptr [rsp+68h],rcx
00007FF6E25C2C55 mov qword ptr [rbp-80h],rax
00007FF6E25C2C59 mov byte ptr [rbp-78h],1
00007FF6E25C2C5D lea rdx,[rsp+60h]
00007FF6E25C2C62 lea rcx,[rbp-80h]
00007FF6E25C2C66 call qword ptr [__imp___std_exception_copy
(07FF6E25CE1C0h)]
00007FF6E25C2C6C lea rax,[std::logic_error::`vftable'
(07FF6E25C9DA0h)]
00007FF6E25C2C73 mov qword ptr [rsp+58h],rax
00007FF6E25C2C78 mov r8,qword ptr [rbp+8]
00007FF6E25C2C7C cmp r8,10h
00007FF6E25C2C80 jb main+0E2h (07FF6E25C2C92h)
00007FF6E25C2C82 inc r8
00007FF6E25C2C85 mov rdx,qword ptr [rbp-10h]
00007FF6E25C2C89 lea rcx,[rbp-10h]
00007FF6E25C2C8D call std::_Wrap_alloc<std::allocator<char>
>::deallocate (07FF6E25C126Ch)
00007FF6E25C2C92 mov qword ptr [rbp+8],0Fh
00007FF6E25C2C9A mov qword ptr [rbp],rbx
00007FF6E25C2C9E mov byte ptr [rbp-10h],bl
00007FF6E25C2CA1 lea
rax,[boost::outcome::v1_std_std::monad_error::`vftable' (07FF6E25C9F08h)]
00007FF6E25C2CA8 mov qword ptr [rsp+58h],rax
00007FF6E25C2CAD movaps xmm0,xmmword ptr [rsp+20h]
00007FF6E25C2CB2 movups xmmword ptr [rsp+70h],xmm0
00007FF6E25C2CB7 lea
rdx,[_TI3?***@v1_std_std@***@boost@@ (07FF6E25CBAB0h)]
00007FF6E25C2CBE lea rcx,[rsp+58h]
00007FF6E25C2CC3 call _CxxThrowException (07FF6E25C4DDEh)
00007FF6E25C2CC8 int 3
00007FF6E25C2CC9 cmp cl,2
00007FF6E25C2CCC sete al
00007FF6E25C2CCF test al,al
00007FF6E25C2CD1 jne main+137h (07FF6E25C2CE7h)
00007FF6E25C2CD3 sub cl,2
00007FF6E25C2CD6 cmp cl,1
00007FF6E25C2CD9 ja main+264h (07FF6E25C2E14h)
00007FF6E25C2CDF test al,al
00007FF6E25C2CE1 je main+264h (07FF6E25C2E14h)
00007FF6E25C2CE7 mov qword ptr [rbp-18h],0Fh
00007FF6E25C2CEF mov qword ptr [rbp-20h],rbx
00007FF6E25C2CF3 mov byte ptr [rbp-30h],bl
00007FF6E25C2CF6 xor r8d,r8d
00007FF6E25C2CF9 lea rdx,[string "" (07FF6E25C9E5Ch)]
00007FF6E25C2D00 lea rcx,[rbp-30h]
00007FF6E25C2D04 call
std::basic_string<char,std::char_traits<char>,std::allocator<char>
>::assign (07FF6E25C1037h)
00007FF6E25C2D09 nop
00007FF6E25C2D0A mov qword ptr [rbp-58h],0Fh
00007FF6E25C2D12 mov qword ptr [rbp-60h],rbx
00007FF6E25C2D16 mov byte ptr [rbp-70h],0
00007FF6E25C2D1A or r9,0FFFFFFFFFFFFFFFFh
00007FF6E25C2D1E xor r8d,r8d
00007FF6E25C2D21 lea rdx,[rbp-30h]
00007FF6E25C2D25 lea rcx,[rbp-70h]
00007FF6E25C2D29 call
std::basic_string<char,std::char_traits<char>,std::allocator<char>
>::assign (07FF6E25C1159h)
00007FF6E25C2D2E movups xmm6,xmmword ptr [rbp-50h]
00007FF6E25C2D32 movups xmmword ptr [rsp+20h],xmm6
00007FF6E25C2D37 lea r8,[rbp-70h]
00007FF6E25C2D3B lea rdx,[rsp+20h]
00007FF6E25C2D40 lea rcx,[rbp+10h]
00007FF6E25C2D44 call std::_System_error::_Makestr
(07FF6E25C11E0h)
00007FF6E25C2D49 cmp qword ptr [rax+18h],10h
00007FF6E25C2D4E jb main+1A3h (07FF6E25C2D53h)
00007FF6E25C2D50 mov rax,qword ptr [rax]
00007FF6E25C2D53 lea rcx,[std::exception::`vftable'
(07FF6E25C9D68h)]
00007FF6E25C2D5A mov qword ptr [rsp+30h],rcx
00007FF6E25C2D5F xor ecx,ecx
00007FF6E25C2D61 mov qword ptr [rsp+38h],rcx
00007FF6E25C2D66 mov qword ptr [rsp+40h],rcx
00007FF6E25C2D6B mov qword ptr [rsp+20h],rax
00007FF6E25C2D70 mov byte ptr [rsp+28h],1
00007FF6E25C2D75 lea rdx,[rsp+38h]
00007FF6E25C2D7A lea rcx,[rsp+20h]
00007FF6E25C2D7F call qword ptr [__imp___std_exception_copy
(07FF6E25CE1C0h)]
00007FF6E25C2D85 lea rax,[std::runtime_error::`vftable'
(07FF6E25C9DC0h)]
00007FF6E25C2D8C mov qword ptr [rsp+30h],rax
00007FF6E25C2D91 mov r8,qword ptr [rbp+28h]
00007FF6E25C2D95 cmp r8,10h
00007FF6E25C2D99 jb main+1FBh (07FF6E25C2DABh)
00007FF6E25C2D9B inc r8
00007FF6E25C2D9E mov rdx,qword ptr [rbp+10h]
00007FF6E25C2DA2 lea rcx,[rbp+10h]
00007FF6E25C2DA6 call std::_Wrap_alloc<std::allocator<char>
>::deallocate (07FF6E25C126Ch)
00007FF6E25C2DAB mov qword ptr [rbp+28h],0Fh
00007FF6E25C2DB3 mov qword ptr [rbp+20h],rbx
00007FF6E25C2DB7 mov byte ptr [rbp+10h],0
00007FF6E25C2DBB lea rax,[std::_System_error::`vftable'
(07FF6E25C9E28h)]
00007FF6E25C2DC2 mov qword ptr [rsp+30h],rax
00007FF6E25C2DC7 movups xmmword ptr [rsp+48h],xmm6
00007FF6E25C2DCC mov r8,qword ptr [rbp-18h]
00007FF6E25C2DD0 cmp r8,10h
00007FF6E25C2DD4 jb main+236h (07FF6E25C2DE6h)
00007FF6E25C2DD6 inc r8
00007FF6E25C2DD9 mov rdx,qword ptr [rbp-30h]
00007FF6E25C2DDD lea rcx,[rbp-30h]
00007FF6E25C2DE1 call std::_Wrap_alloc<std::allocator<char>
>::deallocate (07FF6E25C126Ch)
00007FF6E25C2DE6 mov qword ptr [rbp-18h],0Fh
00007FF6E25C2DEE mov qword ptr [rbp-20h],rbx
00007FF6E25C2DF2 mov byte ptr [rbp-30h],0
00007FF6E25C2DF6 lea rax,[std::system_error::`vftable'
(07FF6E25C9E48h)]
00007FF6E25C2DFD mov qword ptr [rsp+30h],rax
00007FF6E25C2E02 lea rdx,[_TI4?***@std@@
(07FF6E25CB9C8h)]
00007FF6E25C2E09 lea rcx,[rsp+30h]
00007FF6E25C2E0E call _CxxThrowException (07FF6E25C4DDEh)
00007FF6E25C2E13 int 3
00007FF6E25C2E14 mov ebx,dword ptr [rbp-50h]
}
return foo;
00007FF6E25C2E17 mov eax,ebx
}
00007FF6E25C2E19 mov rcx,qword ptr [rbp+30h]
00007FF6E25C2E1D xor rcx,rsp
00007FF6E25C2E20 call __security_check_cookie (07FF6E25C12DFh)
00007FF6E25C2E25 mov rbx,qword ptr [rsp+160h]
00007FF6E25C2E2D movaps xmm6,xmmword ptr [rsp+140h]
00007FF6E25C2E35 add rsp,150h
00007FF6E25C2E3C pop rbp
00007FF6E25C2E3D ret


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas
2016-02-12 00:11:19 UTC
Permalink
On 11 Feb 2016 at 11:57, Michael Marcin wrote:

> On 2/11/2016 5:47 AM, Niall Douglas wrote:
> > outcome<int> _foo; // contains some value
> > int foo;
> > if(_foo) foo=_foo.get();
> > foo=_foo.get_or(5); // also works
>
> I'm not convinced.
>
> I ran a comparison of a trivial implementation of result with your test
> code versus your implementation.
> The results weren't pretty.

I'll caveat everything I'm about to say with a dose of salt as from
past experience my memory of what Stephan told me varies from what he
actually told me. So bearing that in mind as my memory recalls faulty
...

Stephan took a very conservative interpretation of error categories
on the Dinkumware shipped with MSVC, and so you get a large quantity
of "splosh" on MSVC because touching an error category forces
emission of code.

You'll see vastly tighter code on GCC especially, and pretty good on
clang. In the op code counting unit tests there are something like
1000 opcodes on MSVC for six instructions on GCC. It's that bad.

When I raised just how much opcode splosh MSVC generates with
Stephan, he asked "is it any slower?" I benchmarked it, and the
answer is not much, barely measurable, maybe only a few dozen CPU
cycles. It's basically binary bloat, other than that fairly benign.

Stephan has promised to look into the very strict interpretation of
error categories in a future MSVC if the less strict interpretation
on libc++/libstdc++ proves benign in real world usage. tl;dr; a fix
for the bloat generated by MSVC when compiling Outcome is many years
out, if at all.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/
http://ie.linkedin.com/in/nialldouglas/
Emil Dotchevski
2016-02-11 23:20:03 UTC
Permalink
On Wed, Feb 10, 2016 at 11:19 PM, Michael Marcin <***@gmail.com>
wrote:

> On 2/9/2016 3:16 AM, Niall Douglas wrote:
>
>>
>> Something missing from the discussion so far is that
>> expected/result/outcome MUST throw exceptions! If you try to fetch a
>> value from a result and it contains an error, there is no alternative
>> to throwing an exception. This fact is why I don't worry about static
>> function initialisers and just go ahead and use
>> error-code-via-system_error throwing constructors, ultimately you
>> need try...catch in there one way or another.
>>
>>
> There most certainly is an alternative!
> I certainly don't want this behavior.
>
> There's no way that accessing the value of a result<T> without checking
> for an error is not a programming error.
> It should be a precondition that to access the value it needs to not
> contain an error.
> It should *not* be throwing an exception because you cannot fail to
> satisfy the postcondition until you meet the preconditions.
>
> Undefined-behavior is the appropriate specification for accessing a value
> from a result that contains an error.


It depends what's your goal. If you want to avoid logic errors in error
handling code (as you should), then you don't want accessing the result
without checking for error to be undefined behavior.

Consider the following C code:

int * p=(int *)malloc(sizeof(int));
*p=42;

Of course the correct code is:

int * p=(int *)malloc(sizeof(int));
if( !p )
return error;
*p=42;

Niall's motivation is to make code like this safer without resorting to
exceptions. But the best way such code is made safe is by using exceptions.
In C++ you can write:

int * p=new int;
*p=42;

without invoking undefined behavior, because the compiler will
automatically generate code that effectively does:

int * p=new int;
if( !p )
return error;
*p=42;

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas
2016-02-12 00:30:19 UTC
Permalink
On 11 Feb 2016 at 15:20, Emil Dotchevski wrote:

> Niall's motivation is to make code like this safer without resorting to
> exceptions. But the best way such code is made safe is by using exceptions.

Sorry, I have to correct you here as this is an incorrect statement,
and it's my own fault for me not correcting an earlier reply to a
post of mine where you got a false understanding of my position.

Fixed: I *like* exceptions, a lot. Indeed I default to them, as I did
in AFIO v1 for handling all unexpected outcomes. In any new general
purpose C++ I write either now or in the future will exclusively use
exceptions.

However, some of the AFIO v1 peer reviewers wanted a hard semantic
distinction between catastrophic errors and benign failures (I can
see a point here). Other peer reviewers wanted no more than a few
hundred cycles overhead around the raw syscall (I think this daft
personally). Quite a few took issue with the use of shared_ptr to
manage lifetimes (also a daft criticism), and the more insightful
reviewers took particular issue with the high number of allocations
and free per operation performed due to the many stages of type
erasure (I very much agree).

Even though AFIO v2 probably won't go in for peer review on it own, I
have implemented almost all of the peer review feedback. v2 throws no
exceptions, allocates and frees exactly one malloc per operation,
uses no threads and therefore doesn't need shared_ptr, and yes there
are even less than a few hundred cycles overhead around the syscalls
on Windows (not so on POSIX, AIO is shockingly awful and you need to
spend a ton of cycles to make it sane). All possible outcomes from
any AFIO v2 function are as tightly specified as POSIX itself right
down to every potential error emitted.

Outcome is a very large part of achieving this very lightweight AFIO
v2 whilst leveraging as much of the power of C++ as possible in doing
so. I personally speaking think such lightweightness is overkill for
the use cases as file i/o is enormously heavy, but I defer to Boost
peer review judgement. You'll get what you collectively asked for. I
don't personally agree with it, but if one *has* to write this sort
of lightweight C++, then I think Outcome a great help in doing so.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/
http://ie.linkedin.com/in/nialldouglas/
Emil Dotchevski
2016-02-12 02:00:16 UTC
Permalink
On Thu, Feb 11, 2016 at 4:30 PM, Niall Douglas <***@nedprod.com>
wrote:

> On 11 Feb 2016 at 15:20, Emil Dotchevski wrote:
>
> > Niall's motivation is to make code like this safer without resorting to
> > exceptions. But the best way such code is made safe is by using
> exceptions.
>
> Sorry, I have to correct you here as this is an incorrect statement,
> and it's my own fault for me not correcting an earlier reply to a
> post of mine where you got a false understanding of my position.
>
> Fixed: I *like* exceptions, a lot. Indeed I default to them, as I did
> in AFIO v1 for handling all unexpected outcomes. In any new general
> purpose C++ I write either now or in the future will exclusively use
> exceptions.
>
> However, some of the AFIO v1 peer reviewers wanted a hard semantic
> distinction between catastrophic errors and benign failures (I can
> see a point here). Other peer reviewers wanted no more than a few
> hundred cycles overhead around the raw syscall (I think this daft
> personally). Quite a few took issue with the use of shared_ptr to
> manage lifetimes (also a daft criticism), and the more insightful
> reviewers took particular issue with the high number of allocations
> and free per operation performed due to the many stages of type
> erasure (I very much agree).
>
> Even though AFIO v2 probably won't go in for peer review on it own, I
> have implemented almost all of the peer review feedback. v2 throws no
> exceptions, allocates and frees exactly one malloc per operation,
> uses no threads and therefore doesn't need shared_ptr, and yes there
> are even less than a few hundred cycles overhead around the syscalls
> on Windows (not so on POSIX, AIO is shockingly awful and you need to
> spend a ton of cycles to make it sane). All possible outcomes from
> any AFIO v2 function are as tightly specified as POSIX itself right
> down to every potential error emitted.
>
> Outcome is a very large part of achieving this very lightweight AFIO
> v2 whilst leveraging as much of the power of C++ as possible in doing
> so. I personally speaking think such lightweightness is overkill for
> the use cases as file i/o is enormously heavy, but I defer to Boost
> peer review judgement. You'll get what you collectively asked for. I
> don't personally agree with it, but if one *has* to write this sort
> of lightweight C++, then I think Outcome a great help in doing so.
>

I'm not qualified to judge these design decisions, but I'm very skeptical.
You as the author of AFIO are an expert in this domain, or else you
wouldn't be writing this library. It's okay to defer to peer review
judgement as long as it is substantiated.

That said, whether or not a function throws exceptions is a design
decision, nothing to do with run-time overhead. The reason, again, is that
any exception handling overhead of calling a function can be easily
eliminated, by inlining. It is true that this will not make the function
work faster in the case when it does throw, but at that time we're usually
dealing with aborting an operation, it's an exceptional condition. I
understand that this may be a problem in some weird cases but that's when
I'd demand strong evidence rather than conspiracy theories and toy
benchmark programs.

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas
2016-02-12 10:22:48 UTC
Permalink
On 11 Feb 2016 at 18:00, Emil Dotchevski wrote:

> > Outcome is a very large part of achieving this very lightweight AFIO
> > v2 whilst leveraging as much of the power of C++ as possible in doing
> > so. I personally speaking think such lightweightness is overkill for
> > the use cases as file i/o is enormously heavy, but I defer to Boost
> > peer review judgement. You'll get what you collectively asked for. I
> > don't personally agree with it, but if one *has* to write this sort
> > of lightweight C++, then I think Outcome a great help in doing so.
> >
>
> I'm not qualified to judge these design decisions, but I'm very skeptical.
> You as the author of AFIO are an expert in this domain, or else you
> wouldn't be writing this library. It's okay to defer to peer review
> judgement as long as it is substantiated.

As I said during the peer review, people who think they want async
file i/o are generally disappointed. What people really want is
*control* which normally means low worst case latencies, but in the
filing system synchronous i/o is almost always lower worst case
latency than async so firing 10,000 threads at a file i/o problem
might actually be sensible. AFIO v1 delivered oodles of control, but
packed very substantial work into file open and close (both of which
are horribly slow anyway so it made sense) in order to keep
everything else fixed overhead.

Now, I can totally see that if you want to open and close a lot of
files, AFIO v1 is going to be not great for you, and v2 is going to
be a lot better. People got upset with the find regex in files
performance for example, and that was understandable: AFIO v1 is a
poor choice for that use case.

In this sense the v2 design is much more flexible and reusable, but
the end user programmer will have to work far harder than with v1 and
have a far deeper understanding of filing systems to get reasonable
results. The single biggest reason I won't be presenting v2 for peer
review is the enormous documentation which would be required, it's at
least six months of work and probably more, and I just can't afford
such an enormous loss of income.

Still, what I learned from the Boost peer review is people here hate
to be hand held, and I get that. And let me be very clear, the AFIO
v2 design has a lot of merit, I don't disagree with any of it, I just
think it excessive for its likely use cases - it is going to be
several times lighter weight than ASIO which makes zero sense for
file i/o vs socket i/o.

> That said, whether or not a function throws exceptions is a design
> decision, nothing to do with run-time overhead. The reason, again, is that
> any exception handling overhead of calling a function can be easily
> eliminated, by inlining.

If the compiler can see all possible throw sites, and it is not
called MSVC, then yes.

I don't think people's issue is about what happens in practice under
optimisation. They want *guarantees*. They want to see noexcept on
every function because it's a *guarantee* that no matter what the
programmer does, no unbounded execution times will occur.

This is why games, audio and HFT shops ban the STL and disable RTTI
and exceptions. It's about assurance, not reality in practice.

> It is true that this will not make the function
> work faster in the case when it does throw, but at that time we're usually
> dealing with aborting an operation, it's an exceptional condition. I
> understand that this may be a problem in some weird cases but that's when
> I'd demand strong evidence rather than conspiracy theories and toy
> benchmark programs.

A lot of this debate isn't about empirical reality, if C++
programmers were managed by reality everyone would be using the STL
with exceptions and RTTI turned on for years now.

As I mentioned, it's about *assurance*: given a pool of average
programmers, how can you (technical leads and management) be assured
they will write on average low worst case latency C++? This is why I
expect exceptions disabled C++ to remain around for a long time to
come, and why AFIO v2 and Outcome will eventually support exceptions
disabled.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/
http://ie.linkedin.com/in/nialldouglas/
Emil Dotchevski
2016-02-12 21:28:06 UTC
Permalink
On Fri, Feb 12, 2016 at 2:22 AM, Niall Douglas <***@nedprod.com>
wrote:

> On 11 Feb 2016 at 18:00, Emil Dotchevski wrote:
> > That said, whether or not a function throws exceptions is a design
> > decision, nothing to do with run-time overhead. The reason, again, is
> that
> > any exception handling overhead of calling a function can be easily
> > eliminated, by inlining.
>
> If the compiler can see all possible throw sites, and it is not
> called MSVC, then yes.
>

If you inline a function, the compiler can see when it throws and when it
doesn't. Every compiler, even MSVC, will remove the exception handling
overhead, as long as you inline, though of course you will incur overhead
if an exception is actually thrown.


> I don't think people's issue is about what happens in practice under
> optimisation. They want *guarantees*. They want to see noexcept on
> every function because it's a *guarantee* that no matter what the
> programmer does, no unbounded execution times will occur.
>

I know what people want, I've had my share of arguments with people who
want noexcept no matter what.


> This is why games, audio and HFT shops ban the STL and disable RTTI
> and exceptions. It's about assurance, not reality in practice.
>

I know that too. It's a free country, anyone can disable anything they want.


> > It is true that this will not make the function
> > work faster in the case when it does throw, but at that time we're
> usually
> > dealing with aborting an operation, it's an exceptional condition. I
> > understand that this may be a problem in some weird cases but that's when
> > I'd demand strong evidence rather than conspiracy theories and toy
> > benchmark programs.
>
> A lot of this debate isn't about empirical reality, if C++
> programmers were managed by reality everyone would be using the STL
> with exceptions and RTTI turned on for years now.
>

Yep. Recently we shipped a game on Nintendo 3DS, 60 fps stereo, exception
handling and RTTI enabled, STL used throughout. Peter helped me get
boost::shared_ptr work on that platform, evidently we were the first
developer to use Boost on that platform. :)


> As I mentioned, it's about *assurance*: given a pool of average
> programmers, how can you (technical leads and management) be assured
> they will write on average low worst case latency C++? This is why I
> expect exceptions disabled C++ to remain around for a long time to
> come, and why AFIO v2 and Outcome will eventually support exceptions
> disabled.
>

Such arguments do push my buttons so I get involved, but I have very low
tolerance for how much I'd compromise my library designs to accommodate
incompetent technical leads. If you think that you've made your library
harder to use without empirical improvements in performance, isn't that a
problem?

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Michael Marcin
2016-02-12 05:02:47 UTC
Permalink
On 2/11/2016 5:20 PM, Emil Dotchevski wrote:
> On Wed, Feb 10, 2016 at 11:19 PM, Michael Marcin <***@gmail.com>
> wrote:
>
>> On 2/9/2016 3:16 AM, Niall Douglas wrote:
>>
>>>
>>> Something missing from the discussion so far is that
>>> expected/result/outcome MUST throw exceptions! If you try to fetch a
>>> value from a result and it contains an error, there is no alternative
>>> to throwing an exception. This fact is why I don't worry about static
>>> function initialisers and just go ahead and use
>>> error-code-via-system_error throwing constructors, ultimately you
>>> need try...catch in there one way or another.
>>>
>>>
>> There most certainly is an alternative!
>> I certainly don't want this behavior.
>>
>> There's no way that accessing the value of a result<T> without checking
>> for an error is not a programming error.
>> It should be a precondition that to access the value it needs to not
>> contain an error.
>> It should *not* be throwing an exception because you cannot fail to
>> satisfy the postcondition until you meet the preconditions.
>>
>> Undefined-behavior is the appropriate specification for accessing a value
>> from a result that contains an error.
>
>
> It depends what's your goal. If you want to avoid logic errors in error
> handling code (as you should), then you don't want accessing the result
> without checking for error to be undefined behavior.
>
> Consider the following C code:
>
> int * p=(int *)malloc(sizeof(int));
> *p=42;
>
> Of course the correct code is:
>
> int * p=(int *)malloc(sizeof(int));
> if( !p )
> return error;
> *p=42;
>
> Niall's motivation is to make code like this safer without resorting to
> exceptions. But the best way such code is made safe is by using exceptions.
> In C++ you can write:
>
> int * p=new int;
> *p=42;
>
> without invoking undefined behavior, because the compiler will
> automatically generate code that effectively does:
>
> int * p=new int;
> if( !p )
> return error;
> *p=42;

I disagree with your premise that throwing exceptions avoids logic
errors or makes code "safe".

To borrow from Andrzej's blog:
--
One thing that I want to point out is that just checking every possible
“invalid function input” and throwing an exception on it does not
necessarily make the program more correct or safe. It only prevents
crashes, but not bugs. Bugs, in turn, are a sort of UB on the higher
level of abstraction.
--


You likely routinely use "unsafe" code that can lead to UB in the
presence of programming errors.

Do you use std::vector operator[] in your code?
Do you use std::vector iterators?

Maybe you always use std::vector at() to access elements.
If so then I understand your reluctance to deal specify UB.

Writing high performance code requires you to *think*.
In C++ we embrace UB when it is "The Right Thing".

You can certainly add a debug layer to result that can help diagnose
problems (mark if the result was tested for containing a value or error
is some way and assert if it was accessed without such a test).
Much as Microsoft's STL debug iterators are a helpful tool.

But I don't want to pay for them in retail builds.

There's no way I'm going to throw an exception because a raytrace hit an
intersection limit when I need to maintain a consistent 90hz stereo
rendering or risk making my users literally ill. Still it's an error and
needs to be handled gracefully.

If I can't the interface and implementation I want and need out of a
standard or pseudo standard library then I'll have to roll my own but
it's a shame how often I have to do that. It's a large part of why STL
and Boost have terrible reputation in my industry and are outright
banned in many companies.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Emil Dotchevski
2016-02-12 07:39:53 UTC
Permalink
On Thu, Feb 11, 2016 at 9:02 PM, Michael Marcin <***@gmail.com>
wrote:

>
> You likely routinely use "unsafe" code that can lead to UB in the presence
> of programming errors.
>
> Do you use std::vector operator[] in your code?
> Do you use std::vector iterators?
>
> Maybe you always use std::vector at() to access elements.
> If so then I understand your reluctance to deal specify UB.
>

This is not about safe or unsafe code, it's about preventing logic errors.
I'm not arguing that vector::op[] should throw. Actually I'd say that it's
a bug to call vector::at. :)

Writing high performance code requires you to *think*.
> In C++ we embrace UB when it is "The Right Thing".
>

I embrace it where it is appropriate, yes.


> You can certainly add a debug layer to result that can help diagnose
> problems (mark if the result was tested for containing a value or error is
> some way and assert if it was accessed without such a test).
> Much as Microsoft's STL debug iterators are a helpful tool.
>
> But I don't want to pay for them in retail builds.
>

Good, I don't, either.


> There's no way I'm going to throw an exception because a raytrace hit an
> intersection limit when I need to maintain a consistent 90hz stereo
> rendering or risk making my users literally ill. Still it's an error and
> needs to be handled gracefully.
>

And I'm arguing that whether you throw or not should rarely be a question
of performance. If hitting the intersection limit in a raytracing program
leads to an invalid frame, you should report that error by throwing an
exception. This has zero performance cost if functions are being inlined,
which they are at the lowest levels of a raytracer. Generally, if you can
afford the "if" statement to detect the error, you can afford to throw.


> If I can't the interface and implementation I want and need out of a
> standard or pseudo standard library then I'll have to roll my own but it's
> a shame how often I have to do that. It's a large part of why STL and Boost
> have terrible reputation in my industry and are outright banned in many
> companies.


In ~20 years in the video game industry I have never needed to implement my
own STL, and modern STL implementations are a lot better than in the old
days. On the other hand years ago I did have to refactor a company's
home-grown STL replacement to fold templates to reduce bloat, since
otherwise the executable was over 20 megs on a platform with 32 megs of
RAM. Ironically, the STL on that platform was folding the templates
automatically. Too bad they had it banned.

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Michael Marcin
2016-02-12 09:25:21 UTC
Permalink
On 2/12/2016 1:39 AM, Emil Dotchevski wrote:
> On Thu, Feb 11, 2016 at 9:02 PM, Michael Marcin <***@gmail.com>
> wrote:
>>
>> But I don't want to pay for them in retail builds.
>>
>
> Good, I don't, either.

Sorry, I don't follow at all then.

If .get() isn't specified as UB when a value is not ready how are you
going to avoid the overhead?

Specifically .get() must do *at least* an if statement before returning
the value.

Are you relying on the compiler to optimize away the check and throw as
unreachable given an earlier check for an error?

>
> And I'm arguing that whether you throw or not should rarely be a question
> of performance. If hitting the intersection limit in a raytracing program
> leads to an invalid frame, you should report that error by throwing an
> exception. This has zero performance cost if functions are being inlined,
> which they are at the lowest levels of a raytracer. Generally, if you can
> afford the "if" statement to detect the error, you can afford to throw.
>

Sorry I don't follow.
Are you suggesting that the raytracer should be inlined into every call
site?

What I think you're saying is that where I want to write code that looks
like:


result<int> bar()
{
return std::make_error_code( std::errc::bad_address );
}

int main()
{
int foo;

auto _foo = bar();
if ( _foo ) {
foo = _foo.get();
} else {
foo = 0;
}

return foo;
}


I should instead write code that looks like:

int bar()
{
throw std::system_error(
std::make_error_code( std::errc::bad_address ) );
}

int main()
{
int foo;

try {
foo = bar();
}
catch ( std::system_error& e )
{
foo = 0;
}

return foo;
}


And I should expect equal performance?


>
> In ~20 years in the video game industry I have never needed to implement my
> own STL, and modern STL implementations are a lot better than in the old
> days. On the other hand years ago I did have to refactor a company's
> home-grown STL replacement to fold templates to reduce bloat, since
> otherwise the executable was over 20 megs on a platform with 32 megs of
> RAM. Ironically, the STL on that platform was folding the templates
> automatically. Too bad they had it banned.
>

Your experience differs from my own.

EASTL just (officially) released to much fanfare.
https://github.com/electronicarts/EASTL

It's very easy to find performance problems in even current shipping
standard libraries.
2 examples:
See what happens when you insert a duplicate key into a std::map VS2015.
Or look towards the 17x perf increase in iostreams float serialization
coming in VS2015 update 2.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Emil Dotchevski
2016-02-12 20:48:47 UTC
Permalink
On Fri, Feb 12, 2016 at 1:25 AM, Michael Marcin <***@gmail.com>
wrote:

> On 2/12/2016 1:39 AM, Emil Dotchevski wrote:
>
>> On Thu, Feb 11, 2016 at 9:02 PM, Michael Marcin <***@gmail.com>
>> wrote:
>>
>>>
>>> But I don't want to pay for them in retail builds.
>>>
>>>
>> Good, I don't, either.
>>
>
> Sorry, I don't follow at all then.
>
> If .get() isn't specified as UB when a value is not ready how are you
> going to avoid the overhead?
>
> Specifically .get() must do *at least* an if statement before returning
> the value.
>

Calling shared_ptr::get() is always well defined. It contains no ifs.


> And I'm arguing that whether you throw or not should rarely be a question
>> of performance. If hitting the intersection limit in a raytracing program
>> leads to an invalid frame, you should report that error by throwing an
>> exception. This has zero performance cost if functions are being inlined,
>> which they are at the lowest levels of a raytracer. Generally, if you can
>> afford the "if" statement to detect the error, you can afford to throw.
>>
>>
> Sorry I don't follow.
> Are you suggesting that the raytracer should be inlined into every call
> site?
>

I avoid using inline except if the profiler tells me it's needed. If it is
needed, then I inline regardless of whether the function emits exceptions
or not.


> What I think you're saying is that where I want to write code that looks
> like:
>
>
> result<int> bar()
> {
> return std::make_error_code( std::errc::bad_address );
> }
>
> int main()
> {
> int foo;
>
> auto _foo = bar();
> if ( _foo ) {
> foo = _foo.get();
> } else {
> foo = 0;
> }
>
> return foo;
> }
>
>
> I should instead write code that looks like:
>
> int bar()
> {
> throw std::system_error(
> std::make_error_code( std::errc::bad_address ) );
> }
>
> int main()
> {
> int foo;
>
> try {
> foo = bar();
> }
> catch ( std::system_error& e )
> {
> foo = 0;
> }
>
> return foo;
> }
>
>
> And I should expect equal performance?
>

Obviously not, because you're always throwing. Throw may incur overhead,
certainly does on Windows. What I'm saying is that when you don't actually
throw, exception handling overhead occurs exactly where function call
overhead occurs, and can be eliminated completely by inlining.

In ~20 years in the video game industry I have never needed to implement my
>> own STL, and modern STL implementations are a lot better than in the old
>> days. On the other hand years ago I did have to refactor a company's
>> home-grown STL replacement to fold templates to reduce bloat, since
>> otherwise the executable was over 20 megs on a platform with 32 megs of
>> RAM. Ironically, the STL on that platform was folding the templates
>> automatically. Too bad they had it banned.
>>
>>
> Your experience differs from my own.
>
> EASTL just (officially) released to much fanfare.
> https://github.com/electronicarts/EASTL
>
> It's very easy to find performance problems in even current shipping
> standard libraries.
> 2 examples:
> See what happens when you insert a duplicate key into a std::map VS2015.
> Or look towards the 17x perf increase in iostreams float serialization
> coming in VS2015 update 2.


Yes I'm familiar with EASTL. I generally don't care how slow STL is, except
when something shows up on my radar (profiler), in which case usually the
reason is user error. Regardless, consider that if a particular use of
std::map is too slow, you don't need a faster std::map, you need a faster
implementation only for the operations needed in this particular use of
std::map. Such implementation can be a lot faster than STL or EASTL because
it isn't generic.

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Michael Marcin
2016-02-13 05:02:28 UTC
Permalink
On 2/12/2016 2:48 PM, Emil Dotchevski wrote:
> On Fri, Feb 12, 2016 at 1:25 AM, Michael Marcin <***@gmail.com>
> wrote:
>>
>> If .get() isn't specified as UB when a value is not ready how are you
>> going to avoid the overhead?
>>
>> Specifically .get() must do *at least* an if statement before returning
>> the value.
>>
>
> Calling shared_ptr::get() is always well defined. It contains no ifs.
>

Okay. I don't see how that's relevant at all.

>>>
>> Sorry I don't follow.
>> Are you suggesting that the raytracer should be inlined into every call
>> site?
>>
>
> I avoid using inline except if the profiler tells me it's needed. If it is
> needed, then I inline regardless of whether the function emits exceptions
> or not.
>

Let me be more explicit.

You certainly wouldn't inline a raytracer (a heavy operation) at every
call site, you would just horribly bloat your executable for no benefit
and likely achieve a degradation of performance.

However the premise is that the code can have errors in cases which are
not unexpected (aka exceptional), which are not the result of
programming errors (logic errors), which occur because of limits
specifically put in place to constrain time and memory consumption of
the operation (performance is important even in the error control flow).

Note also that this is just an example, there are many other similar
scenarios that could be explored instead.

>
>> What I think you're saying is that where I want to write code that looks
>> like:
>>
>>
>> result<int> bar()
>> {
>> return std::make_error_code( std::errc::bad_address );
>> }
>>
>> int main()
>> {
>> int foo;
>>
>> auto _foo = bar();
>> if ( _foo ) {
>> foo = _foo.get();
>> } else {
>> foo = 0;
>> }
>>
>> return foo;
>> }
>>
>>
>> I should instead write code that looks like:
>>
>> int bar()
>> {
>> throw std::system_error(
>> std::make_error_code( std::errc::bad_address ) );
>> }
>>
>> int main()
>> {
>> int foo;
>>
>> try {
>> foo = bar();
>> }
>> catch ( std::system_error& e )
>> {
>> foo = 0;
>> }
>>
>> return foo;
>> }
>>
>>
>> And I should expect equal performance?
>>
>
> Obviously not, because you're always throwing. Throw may incur overhead,
> certainly does on Windows. What I'm saying is that when you don't actually
> throw, exception handling overhead occurs exactly where function call
> overhead occurs, and can be eliminated completely by inlining.

I thought it was obvious that that code is trying to illustrate a
difference in interface. Obviously bar() is meant to do work that
returns an int but sometimes can't and results in an error.

If you're going to choose exceptions to report your errors you're not
going to be able to avoid actually throwing because errors *will* occur
(as that is the whole premise of this thread).




_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Emil Dotchevski
2016-02-13 05:36:50 UTC
Permalink
On Fri, Feb 12, 2016 at 9:02 PM, Michael Marcin <***@gmail.com>
wrote:

>
> Sorry I don't follow.
>>> Are you suggesting that the raytracer should be inlined into every call
>>> site?
>>>
>>>
>> I avoid using inline except if the profiler tells me it's needed. If it is
>> needed, then I inline regardless of whether the function emits exceptions
>> or not.
>>
>>
> Let me be more explicit.
>
> You certainly wouldn't inline a raytracer (a heavy operation) at every
> call site, you would just horribly bloat your executable for no benefit and
> likely achieve a degradation of performance.
>

Yes, so don't inline the whole thing, do pay the exception handling
overhead at the point when you call this high level function. The overhead
at this call point will be negligible, because as you say it's a heavy
operation. As you call deeper into the raytracer you will get to levels
where the exception handling and function call overhead will be
significant, and you'll inline those functions.


> However the premise is that the code can have errors in cases which are
> not unexpected (aka exceptional), which are not the result of programming
> errors (logic errors), which occur because of limits specifically put in
> place to constrain time and memory consumption of the operation
> (performance is important even in the error control flow).
>

Yes.


> What I think you're saying is that where I want to write code that looks
>>> like:
>>>
>>>
>>> result<int> bar()
>>> {
>>> return std::make_error_code( std::errc::bad_address );
>>> }
>>>
>>> int main()
>>> {
>>> int foo;
>>>
>>> auto _foo = bar();
>>> if ( _foo ) {
>>> foo = _foo.get();
>>> } else {
>>> foo = 0;
>>> }
>>>
>>> return foo;
>>> }
>>>
>>>
>>> I should instead write code that looks like:
>>>
>>> int bar()
>>> {
>>> throw std::system_error(
>>> std::make_error_code( std::errc::bad_address ) );
>>> }
>>>
>>> int main()
>>> {
>>> int foo;
>>>
>>> try {
>>> foo = bar();
>>> }
>>> catch ( std::system_error& e )
>>> {
>>> foo = 0;
>>> }
>>>
>>> return foo;
>>> }
>>>
>>>
>>> And I should expect equal performance?
>>>
>>>
>> Obviously not, because you're always throwing. Throw may incur overhead,
>> certainly does on Windows. What I'm saying is that when you don't actually
>> throw, exception handling overhead occurs exactly where function call
>> overhead occurs, and can be eliminated completely by inlining.
>>
>
> I thought it was obvious that that code is trying to illustrate a
> difference in interface. Obviously bar() is meant to do work that returns
> an int but sometimes can't and results in an error.
>
> If you're going to choose exceptions to report your errors you're not
> going to be able to avoid actually throwing because errors *will* occur (as
> that is the whole premise of this thread).


Let's say in your example, the function throws 50% of the times you call
it. Assuming this isn't an anomaly, it means that you're not really
throwing to indicate a failure, but to report a result. Obviously that's
not a good design.

If your function throws to indicate a failure, then the error path will
have some overhead, but the normal execution will have zero overhead, as
long as the function is inlined.

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Michael Marcin
2016-02-13 05:53:39 UTC
Permalink
On 2/12/2016 11:36 PM, Emil Dotchevski wrote:
>
> Let's say in your example, the function throws 50% of the times you call
> it. Assuming this isn't an anomaly, it means that you're not really
> throwing to indicate a failure, but to report a result. Obviously that's
> not a good design.
>

And this is exactly the sort of situation I would want to use result<T>
in instead of throwing an exception.

I want discriminated union type that is optimized in interface and
implementation specifically for transporting a T or an error code.
And, if in the design a choice must be made, favors T slightly.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Emil Dotchevski
2016-02-13 06:08:28 UTC
Permalink
On Fri, Feb 12, 2016 at 9:53 PM, Michael Marcin <***@gmail.com>
wrote:

> On 2/12/2016 11:36 PM, Emil Dotchevski wrote:
>
>>
>> Let's say in your example, the function throws 50% of the times you call
>> it. Assuming this isn't an anomaly, it means that you're not really
>> throwing to indicate a failure, but to report a result. Obviously that's
>> not a good design.
>>
>>
> And this is exactly the sort of situation I would want to use result<T> in
> instead of throwing an exception.
>
> I want discriminated union type that is optimized in interface and
> implementation specifically for transporting a T or an error code.
> And, if in the design a choice must be made, favors T slightly.


If your point is that in case a function "fails" 50% of the times it's
called, one should not be throwing an exception to discriminate between
success and failure, I agree; OTOH in this case the "error code" vocabulary
doesn't seem appropriate.

Emil

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Michael Marcin
2016-02-13 07:50:31 UTC
Permalink
On 2/13/2016 12:08 AM, Emil Dotchevski wrote:
>
> If your point is that in case a function "fails" 50% of the times it's
> called, one should not be throwing an exception to discriminate between
> success and failure, I agree; OTOH in this case the "error code" vocabulary
> doesn't seem appropriate.
>

If instead I told you I had a function that is called 100k times per
frame and that function returns an error code 1% of the time, would it
make more sense?
It returns a result<T> that holds a T 99% of the time.

I don't think you would advocate throwing 1000 exceptions a frame.



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Domagoj Saric
2016-02-07 18:45:18 UTC
Permalink
On 6.2.2016. 12:53, Vicente J. Botet Escriba wrote:
> Hi,
>
> in case this could help. There were two reports on this subject.
> "Handling Disappointment in C++" by L. Crowl [1] and the other in the
> draft state "Survey of Error Handling" by N. Bolas [2].

Thanks Vicente ;)


--
"What Huxley teaches is that in the age of advanced technology,
spiritual devastation is more likely to come from an enemy with a
smiling face than from one whose countenance exudes suspicion and hate."
Neil Postman


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Loading...