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).
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/
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.
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/
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
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
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
#include 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/
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.
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
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/
On 4 February 2016 at 08:16, Niall Douglas
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.
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.
On 4 February 2016 at 13:45, Michael Marcin
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.
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/
On 4 February 2016 at 15:22, Niall Douglas
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
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
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).
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/
On 4 February 2016 at 23:00, Niall Douglas
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.
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.
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
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
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/
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
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...
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
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
. Although I haven't done tests I'd be reasonable confident returning this by value would perform worse than returning std::pair . 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
? 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/
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.
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/
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
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
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
. Although I haven't done tests I'd be reasonable confident returning this by value would perform worse than returning std::pair . 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
? 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
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... 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
participants (7)
-
Bjorn Reese
-
Domagoj Saric
-
Gavin Lambert
-
Michael Marcin
-
Niall Douglas
-
Robert Ramey
-
Sam Kellett