ned14 / status-code

Proposed SG14 status_code for the C++ standard

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Make system_error2::error throwable/catchable

nobugs-hare opened this issue · comments

Currently, when trying to catch(system_error2::error), a compile-time error is raised (due to lack of copy constructor in errored_status_code<erased> which is in turn because it is deleted in status_code<erased>).

Allowing to throw/catch system_error2::error right now (i.e. without waiting until P0709 mechanics is implemented in C++23 compilers) is very important as it would allow (us and others) to start using throw/catch semantics outlined in P0709 right now, so (a) for the time being the code will work using usual C++ exceptions (non-deterministic but still acceptable for lots of projects), (b) whenever P0709 optimized and deterministic mechanics materialize, we-and-others won't need to rewrite all the error handling logic to P0709 semantics, and (c) such example uses may help to shape the requirements for the public APIs of system_error2::error as they may arise in practical use (while we don't really expect much, you never know until you try).

This would be an intentional and deliberate wontfix I am afraid. Otherwise code ends up out there which expects an error to be treated with dynamic exception specifications, which in turn means the committee can't change that behaviour because it would break existing code, specifically that code which you'd guys write if I changed this.

I appreciate this is very frustrating. But the current canonical way of throwing an error is to call its .throw_exception() member function. That will throw the dynamic exception specified by its error domain for that error, which almost certainly ought to preserve the original error information.

But the current canonical way of throwing an error is to call its .throw_exception() member function

This doesn't seem to be in line with P0709, which we're currently treating as a (kinda) authoritative source on the subject of New Shiny Deterministic Exceptions (for the lack of anything better). From P0709R0:

try{
  ...
  throw arithmetic_error::something;
}
...
catch(error err) {...
}

, with no throw_exception() in sight.

This is essentially all we really want to do - to use exactly the same syntax which will be used in the future, so the app-level code is kinda future-proof and hopefully won't need to be changed (for the time being this syntax will default to existing dynamic exceptions which is not That Big Deal(tm) for 95% of code anyway, it is that remaining 5% which the whole thing is about; as for the need to change the way how the errors are defined - we don't care as it is very local code anyway, with no need to go through tons of existing app-level code to change). We already have our own "simulated" class error which allows for use-as-described-in-P0709, and it will work fine for our purposes, but IONSHO it is a Big Mortgage-Crisis Size Mistake(tm) to force people to wait with writing good code until P0709 makes it into compilers (which in the best case will be like 23 or so, I guess ). Among other things, using proposed patterns in real-world code will allow to see for any deficiencies/needed-improvements/... - which is always a Good Thing(tm).

http://wg21.link/P1095 would be a more appropriate proposal paper than P0709. P0709 proposes an idea, not an implementation. P1095 proposes one of many possible implementations of P0709.

In the end, compilers don't support throwing move-only types, and proposed error is move-only, so it can't work in current compilers. But the current rumour is that at least one of the major compilers will implement some form of P0709 as early as this year as an experimental feature. So you've not long to wait.

You can deploy this library right now into C++ 11 code with great results. I have done this, and this library is a delight to work with as compared to <system_error>. Sure, it's a bit more manual typing, but you can get all the semantics of zero overhead exceptions in C++ 11. If combined with https://github.com/ned14/outcome (which ships with this library in experimental), you can avoid most of the typing too and just get on with writing great performance code.

Thanks for the link to P1095, but as I read P1095, it still recommends to throw std::error, so there is no change in this regard from P0709 (and FWIW, there is no such thing as throw_exception() in P1095 either).

compilers don't support throwing move-only types

Nitpicking: as I understand, compilers do support throwing move-only types; what they don't support is catching_them_by_value . I can be wrong here though.

proposed error is move-only

I don't think there is a requirement to have it move-only (requirement to be move-relocating doesn't prohibit a copy constructor). In fact, we did implement our own class error (two-pointer one, and move-relocating too) which can be thrown and caught by value (maybe it does have an unnecessary virtual call on comparison, but most likely this is because we're too lazy to jump through all the template-related hoops; also, a single virtual call does have deterministic time even if it is not strictly necessary).

at least one of the major compilers will implement some form of P0709 as early as this year

Thanks for the info, we'll keep an eye on it, but being an open-source project aiming to get some popularity, we have to support at least all of Big Three compilers (which we do ATM).

If combined with https://github.com/ned14/outcome (which ships with this library in experimental), you can avoid most of the typing too and just get on with writing great performance code.

Unfortunately, doing so will proliferate dual should-we-return-or-should-we-throw error-handling culture which P0709 is arguing against, and we're firmly on the side on P0709 in this regard.

What we want (as a migration path/stop-gap/...) is simple: (a) start to teach our users "how to use new good P0709-style exceptions", (b) make sure their code already works today - albeit in a non-deterministic manner for the time being, and (c) have reasonable expectations that their throw/catch code (the one which is thinly spread over millions of LoC) won't need to be changed when new shiny compilers will magically make their already-existing exception handling code deterministic (we can and will rewrite our code, but requiring to rewrite app-level code written on top of us, is a disaster which actually never happens in the real world; asking them to re-write a few hundreds of LoC where they merely define their own exception types, is a different story and is perfectly viable).

An alternative approach of "oh, use return-based outcome for the time being and rewrite all the thousands of places - and all function return types(!) - into recommended-by-P0709-and-P1095 throw-exception-style when new shiny compilers appear" isn't a good idea for LOTS of our potential users for half a dozen of reasons (to start with, being representative of general programmer population, 95% of them could not care less about exceptions being deterministic, so forcing them to jump through the hoops for no reason, and moreover, forcing them to deviate from best practices such as E.2 of Core Guidelines, is a Bad Idea(tm)).

As a result, if making your implementation of std::error copyable is not viable - we will have to stick to our own class error (which is admittedly ugly and lacks quite a few features, but it will have to do for the time being).

Nitpicking: as I understand, compilers do support throwing move-only types; what they don't support is catching_them_by_value . I can be wrong here though.

Correct. You can't catch a move-only type by value, only by lvalue (and not rvalue) reference.

I don't think there is a requirement to have it move-only (requirement to be move-relocating doesn't prohibit a copy constructor).

It is correct that move relocating types may have a copy constructor. However proposed std::error also type erases its payload, and that erasure only requires move relocatability of the original type. It does not require copy constructibility. So, for any given instance of std::error, it cannot know if the original type can do anything in addition to moves. Therefore it must be move-only.

This isn't a problem in practice if you know it before you write the code.

What we want (as a migration path/stop-gap/...) is simple: (a) start to teach our users "how to use new good P0709-style exceptions", (b) make sure their code already works today - albeit in a non-deterministic manner for the time being, and (c) have reasonable expectations that their throw/catch code (the one which is thinly spread over millions of LoC) won't need to be changed when new shiny compilers will magically make their already-existing exception handling code deterministic (we can and will rewrite our code, but requiring to rewrite app-level code written on top of us, is a disaster which actually never happens in the real world; asking them to re-write a few hundreds of LoC where they merely define their own exception types, is a different story and is perfectly viable).

In this situation, I'd use macros myself. XXX_TRYBEGIN(), XXX_CATCH(), XXX_TRYEND(). Very easy to regex find and replace in the future. On older compilers, XXX_CATCH() can catch by reference internally.

An alternative approach of "oh, use return-based outcome for the time being and rewrite all the thousands of places - and all function return types(!) - into recommended-by-P0709-and-P1095 throw-exception-style when new shiny compilers appear" isn't a good idea for LOTS of our potential users for half a dozen of reasons (to start with, being representative of general programmer population, 95% of them could not care less about exceptions being deterministic, so forcing them to jump through the hoops for no reason, and moreover, forcing them to deviate from best practices such as E.2 of Core Guidelines, is a Bad Idea(tm)).

If you don't care about performance in the failure handling path, then sure.

If you do care, then globally disable exceptions, require noexcept on every function, and something like Outcome will deliver you what you need.

Core Guidelines are what they are, but parts of them make no sense for fully deterministic code. Ironically enough I was the one assigned from SG14 to fix up the guidelines for low latency users, but I'll admit to complete laziness. I haven't done it.

As a result, if making your implementation of std::error copyable is not viable - we will have to stick to our own class error (which is admittedly ugly and lacks quite a few features, but it will have to do for the time being).

It isn't and won't be copyable because that's the correct design choice. You cannot transport an erased unique ptr, for example, if error is copyable. And the whole point here is that error can transport unknown payload which is safely type erased. Do note that the erased status code in this library does have a .clone() member function, but it is permitted to fail.

Me personally I'd strongly recommend that you bite the correctness bullet and accept error must be move-only. I've found it a wonderful discipline in my own code. But if that's unacceptable, I'd simply define a status_code<T> where T is some common type in my code base and define implicit conversions from your supported payloads into T via make_status_code(). Then you get your copyability, and it all works swimmingly.

Do remember that P0709 if implemented via P1095 allows the static throw and catch of any T. It doesn't have to be std::error, just convertible to std::error if needed (technically speaking, it's actually statically convertible to whatever the throw type is of functions higher in the call stack. We actually would never require std::error except through convention).

@hsutter You may find this issue discussion of use when debating P0709 at Kona. Niall

However proposed std::error also type erases its payload, and that erasure only requires move relocatability of the original type. It does not require copy constructibility.

As I understand, the real issue here is about "how we represent existing=legacy C++ exceptions which don't need to be copy constructible" - but it can be solved, say, by using something analogous to shared_ptr<> (though it can be implemented without multithreaded stuff and in a perfectly deterministic manner too)... In other words, it is not strictly necessary to require std::error to be non-copy-constructible even if it can hold erased non-copy-constructible types (!).

You cannot transport an erased unique ptr, for example, if error is copyable.

See above - I (or rather you) can still easily transport an erased shared ptr; and as transporting a type-erased unique_ptr seems to be merely an implementation detail rather than a requirement (a real requirement is to transport existing exceptions which don't need to be copiable) - it seems to mean that making it copyable is perfectly viable within existing requirements (it is just your specific implementation which elected not to do it)...

In this situation, I'd use macros myself.

Going against core guidelines and best practices, teaching bad style (and there is a consensus that macros ARE bad style when alternatives are present), and requiring to rewrite thousands of LoC of app-level code for no apparent reason are clearly not a good thing at least for our purposes.

If you don't care about performance in the failure handling path, then sure.

We do care about performance in the failure handling path, but only provided that it comes for free for our users' app-level code, as is the case with P0709. That's the key difference between me and you: you're assuming that your users do care about time-deterministic exceptions, but as std::error is intended to a MUCH wider audience than outcome<> - it does have to be usable by that wider audience (95+% of which doesn't care at all about exceptions being time-deterministic), and it is that wider audience which I don't want to inconvenience without a good reason (though if we can have time-determinism for free - it is still a Good Thing(tm) because why not?).

Me personally I'd strongly recommend that you bite the correctness bullet and accept error must be move-only.

Adhering to being move-only is certainly not a problem for our own errors; the only problem with this approach is that then, with current compilers our users won't be able to catch error by value, and catching by value seems to be the recommended way by P0709 (with seemingly has deep roots in its philosophy too, as its whole point seems to throw values). As I already said, we want to teach our users the best practices (which also will stay best practices for a while), and using your specific library is certainly not high on the list of our priorities (why we should do it if your library doesn't fit our requirements? it would be a case of irresponsible re-use which universally causes more trouble than it is worth), so at the moment we'd rather roll out our own copyable implementation of error (in the extreme case we can just wrap your implementation into a shared_ptr, which will make it copyable at about no cost for us, but ATM we're certainly not frustrated enough to do this kind of stuff).

In the future, when respective compilers start to support catching std::error by value, we'll gladly switch to non-copyable-but-catchable-by-value std::error (whatever implementation is accepted by The Almighty Committee), but for the time being, we feel that our migration approach is better than trying to force our users to write app-level code which is different from how-the-good-code-should-eventually-look-like.

I appreciate the additional feedback, but in the end you are trying to shoehorn a new mechanism (move-only exception values) into an existing mechanism (dynamic exception throws of types) where the language does not yet have the requisite support for the new mechanism. You aren't willing to use a library based solution which does work with today's compilers (Outcome). You aren't willing to use macro hacks.

In short, you want tomorrow's C++ today. As do we all. But as with every major piece of C++ out there today, from the very first line you write you now have embedded technical debt, because you have chosen today's C++ which is instantly obsolete tomorrow. That's the price of a language and ecosystem which evolve over time. If you don't want that, there are languages and ecosystems which have barely changed in 40 years which may suit your needs better.

FWIW: thinking a bit more about it, and regardless of our project: I am sure that C++ developers would appreciate new shiny exceptions to be copyable (especially as, as I argued above, there is no reason except for implementation details of your current library to have them move-only, and it feels quite natural to want to copy an exception - with copying semantics being very obvious, as an exception is merely a bunch of bits in memory). I have absolutely no doubt that if we conduct a survey asking about exceptions being copyable, we'll get an almost-unanimous "sure, we want them to be copyable". BTW, given that I won't have more pressing matters for upcoming issues of Overload, I may want to argue in my column there that a requirement for std::error to be copyable should be mentioned in the standard.

P.S. As for the technical debt - of course, we'll create it. It is just that all the solutions you proposed will create much more of such technical debt (return-based style is not appropriate for the vast majority of programs, and macros are plain ugly and hurt readability a lot; moreover, both approaches will create an immense debt in millions of different places of the code, while our ugly patch will create debt in only a few well-defined places, which is a major difference for real-world industry-grade million-LoC projects). As for you're naming "shoehorning" for what we're naming "providing a viable migration path" - it is just a different name for the same thing (and one MUST NOT underestimate issues related to migration path being smooth, this is for sure).

I am sure that C++ developers would appreciate new shiny exceptions to be copyable

That's the same thing as asking people if they'd like free money and world peace. Of course it's popular. But the real question is what's the tradeoff?

(especially as, as I argued above, there is no reason except for implementation details of your current library to have them move-only, and it feels quite natural to want to copy an exception - with copying semantics being very obvious, as an exception is merely a bunch of bits in memory).

I think you are operating under a major misassumption.

Move relocatable types can be bit copied, same as trivially copyable types, but they have non-trivial destructors which MUST be called, otherwise it's UB. This permits std::error to carry around type erased non-trivially-copyable types such as shared ptrs, unique ptrs, exception ptrs, even a std::vector, as all those non-trivially-copyable types meet move relocatability.

One can, of course, also transport trivially copyable types inside std::error as well as all trivially copyable types are also move relocatable types.

If you were to make std::error copyable (and remember, it does already have a .clone() function which will attempt a copy if the non-erased underlying type is capable of that), then some other characteristic of std::error would need to be exchanged for that. Specifically you must give up one of the following:

  1. Type erasure, so std::error gains knowledge of the type of its payload.
  2. No-malloc, so std::error now dynamically allocates memory to erase the type of its payload.

The proposed library already implements (1) via non-erased status_code<>, and under P1095's implementation of P0709, you can throw and catch those typed editions of status code just fine.

The proposed library also already implements (2) via make_status_code_ptr(), which wraps a typed status code into a std::error using dynamic memory allocation, and "springs out" the original typed status code when the std::error is caught and handled.

The point I am making here is that there are three axes of tradeoff in the design of std::error, and this library already provides three clearly defined interoperating variants of status code which exchange one axis for benefits in the other two axes. Under P1095, you choose your poison at the point of use, and accept the tradeoffs. The compiler will refuse to compile incorrectness, and all is good.

I may want to argue in my column there that a requirement for std::error to be copyable should be mentioned in the standard.

If WG21 smile on P0709, then the throw and catching of values rather than types means that the throw of a move-only type can be caught by value. This is because we will have changed the throw-catch semantics for value-based throws to have the move constructor and destructor called per stack frame unwound between the throw site and the catch site (hence the importance of move relocatability, as we would really like the move of a std::error to be a trivial bit copy).

So then you get the syntax as proposed in P0709. But the language doesn't support this yet. You can't make the std::error in this library magically dance the way you want without language support. And you can't make it not move-only for all the reasons I explained above.

Now it's possible that WG21 will strongly want std::error to be copyable. But the tradeoff is that std::error can no longer transport legacy exception throws and a raft of other stuff. So I'd seriously doubt they'll agree to that, once they've thought it through.

I have a fair bit of experience now using Outcome with a move-only E type (which causes outcome::result<T> to always be move-only). Initially it's a real culture shock, it seems very weird that functions returning a copyable T suddenly get move-only outcome::result<T> . But to be honest after a while you get used to it, and it's not a problem in practice.

It is just that all the solutions you proposed will create much more of such technical debt ...

I've received a LOT of email from mainly the finance community in recent months trying to create some form of P0709 now and today, and without using dynamic exceptions nor Outcome.

Inevitably they all run into the same problems and face the same tradeoffs which led to Outcome's and this status code library's present design. So tl;dr; please don't try to innovate locally. Just choose one of the established choices, because I haven't seen a local innovation yet which wasn't far worse than the established choices.

(If you really hate all options before you, you might consider https://zajo.github.io/leaf/ which counterarguments Outcome's design, though it has not passed a Boost review, and has very serious problems like relying heavily on TLS which is a showstopper for a lot of folk on embedded or GPUs or in constexpr)

No-malloc, so std::error now dynamically allocates memory to erase the type of its payload.

Ok, but it will be per-domain allocation (so only some of the domains will need to allocate), am I right? And you want to say that allocations are considered bad enough even for legacy C++ exceptions (I clearly don't think so, even in most of embedded stuff which doesn't use legacy C++ exceptions in the first place)?

it does already have a .clone() function which will attempt a copy if the non-erased underlying type is capable of that

We didn't manage to make clone() work, but assuming that it does indeed work - what prevents from using it to make std::error copyable? Even if it will fail for non-copyable exceptions (and I've never seen a non-copyable exception in my 20+ years of C++ software architect, so it is more of a theoretical issue) - I'd argue that it will be a pretty good deal.

So tl;dr; please don't try to innovate locally.

May I ask why are you so confident that your non-standard locally-innovated solution is better than ours? Until The Almighty Committee has spoken, all the solutions under the sun are equal, don't you agree?

Ok, but it will be per-domain allocation (so only some of the domains will need to allocate), am I right? And you want to say that allocations are considered bad enough even for legacy C++ exceptions (I clearly don't think so, even in most of embedded stuff which doesn't use legacy C++ exceptions in the first place)?

No dynamic memory allocation ought to be needed, ever, under P1095 and this library design. Including within code domains.

You're probably thinking that you could have the domain do a once-off memory allocation, and hand off bits to std::error instances in order to make them copyable. But that's dynamic memory allocation! And as I mentioned, you can do that if you want via some mechanism like make_status_code_ptr(). The design accommodates that. But I think defaulting to that is a bad idea. Copyable std::error is not worth that price. Just accept it's move-only, and get on with life. It's actually fine in practice.

We didn't manage to make clone() work, but assuming that it does indeed work - what prevents from using it to make std::error copyable?

So you're proposing that std::error's copy constructor call the domain's clone function every copy constructor?

That would eliminate the ability to trivially bit copy, which would be a bad idea seeing as these objects need to get moved or copied every stack frame unwound.

May I ask why are you so confident that your non-standard locally-innovated solution is better than ours? Until The Almighty Committee has spoken, all the solutions under the sun are equal, don't you agree?

Status code was designed by SG14, the WG21 low latency study group after more than a year of stakeholder analysis, many many meetings, and much discussion of many alternative approaches.

Significant design aspects of status code have been backported into Boost.System, and are expected to be merged into std::error_code for C++ 23. Both Boost and the committee like what they have seen so far in status code.

Status code has had lots of real world testing by lots of people over the past year to knock out kinks, and it will be shipped in Boost experimental from 1.70 onwards.

In the end, it's up to each person to decide for themselves whether a committee-designed, Boost-sanctioned library is better than a local innovation. I've seen lots of local reinventions, the only one I'd give any credit to is Emil's LEAF so far, and he's a widely respected long established Boost developer. I'm happy to be proven wrong if shown a fully working improvement to the established designs, you'd find me the first to advocate something better than what we currently have for standardisation.

But until I'm shown a fully working improvement, I don't believe they are possible with the current language's limitations.

Copyable std::error is not worth that price. Just accept it's move-only, and get on with life.

This is just one man's subjective opinion; FWIW, as an another man's subjective opinion, I have an exactly opposite one (that having an extra allocation only for non-copyable legacy C++ exceptions is not a problem at all; in particular, because that code which uses them now, is non-deterministic and extremely-inefficient-along-the-failure-path anyway). Do you have any doubts what a survey of developers would say about this choice?

It's actually fine in practice.

I think that we already did have an agreement that developers DO want copyable std::error (as long as there are no serious drawbacks). As a result, arguments along the lines of "heck, it is not TOO bad, you just have to accept it because I said so" don't really fly.

Status code was designed by SG14...

TBH, I don't have too many problems with status_code as such; it is std::error which causes me headaches - and BTW I feel that a fact that in your whopping 3 paragraphs on SG14 work on status code you didn't mention any significant discussion on std::error, is rather telling.

That would eliminate the ability to trivially bit copy, which would be a bad idea seeing as these objects need to get moved or copied every stack frame unwound.

While unwounding, exceptions have to be moved, and not copied ; there is a reason why P0709 requires them to be bit-movable, and not necessarily bit-copyable. So I STILL don't see why this argument has any bearing on the question of std::error being copyable (="I still don't see any realistic drawbacks in making std::error copyable"). Moreover, as your design doesn't have copy constructor at all, it already relies on exceptions being moved, not copied!

But until I'm shown a fully working improvement, I don't believe they are possible with the current language's limitations.

But you still didn't say why it is not possible, did you? Arguments along the lines of "I don't see why an alternative won't work, but I refuse to do anything to improve current implementation" are fully within your rights, but will certainly be an extremely weak argument for even an SG14 meeting, leave alone voting WG21 one.

This is just one man's subjective opinion

No. The present design came from (the relevant part of) the standards committee; it received a unanimous formal acceptance vote from SG14; Herb's P0709 as a result strongly recommends std::error to be move relocatable. The present design is the subjective consensus opinion of many of the world's experts in this domain. It is believed to be optimal, given the constraints in play.

The only complaints I have heard from the committee regarding this library is the appearance of over engineering. Some find it too flexible, and don't think it adds enough value over <system_error>. That's a very valid argument against standardising this, but it's not a suboptimal engineering one.

I think that we already did have an agreement that developers DO want copyable std::error (as long as there are no serious drawbacks). As a result, arguments along the lines of "heck, it is not TOO bad, you just have to accept it because I said so" don't really fly.

I have explained the tradeoffs in play, and there are very serious drawbacks to copy constructible std::error.

in your whopping 3 paragraphs on SG14 work on status code you didn't mention any significant discussion on std::error, is rather telling.

That's because there wasn't a discussion on that design point. SG14 wanted a std::error_code without all the problems listed in the stakeholder analysis in P0824. Some felt that a long payload was sufficient, others felt a bitcasted type erasure would be better. Nobody disputed the erasure of payload polymorphism into the category/domain like error codes already do. It didn't even come up.

As I have already explained several times now, this library provides multiple choices to end users. They can throw typed status codes, bitcast erased status codes, or dynamic allocated erased status codes. The default is bitcast erased status codes. They have an excellent balance of hard determinism, cognitive load, and ease of use. No they are not as easy to use as dynamic exceptions currently, but if we get generic matchers into the language, then they will be.

Arguments along the lines of "I don't see why an alternative won't work, but I refuse to do anything to improve current implementation" are fully within your rights, but will certainly be an extremely weak argument for even an SG14 meeting, leave alone voting WG21 one.

As I have explained many times now, SG14 formally voted assent on this design. WG21 won't examine the design until Cologne, but all the informal feedback I've seen so far is very positive. As I have mentioned, some design elements have been back ported into Boost and the standard. Even WG14 (C programming language) examined this design as part of seeing how to implement P0709 support in C, and it was found to be solid and useful, indeed a magical form of status code may yet end up in the C programming language.

I am seeing lots of hand waving and "wouldn't it be nice if?" postulation here from you. I don't find any of it convincing.

If you think you know better, please show me working proof. Otherwise you're just engaging in fantasy and speculation.

Emil was challenged back during his objections to Outcome to show me working proof of better than Outcome. He came up with LEAF, and well done on him for doing so. His is a worthy effort, a different balancing of tradeoffs. It's easy to complain about the reality of tradeoffs. It's hard to improve significantly on a balance of tradeoffs.

Nevertheless I'll be glad to see working proof of me, and the SG14 study group, being wrong.

Basically, what you're saying is that SG14 has voted on the whole proposal without spending much time discussing a (supposedly-not-so-important-for-SG14) part of the proposal - the part concerning std::error (which is BTW consistent with the feeling I got from your code). And at least for me it clearly means that this discussion-which-not-happened-yet (on specifics of std::error) should take place somewhere (probably in WG21 as exceptions - unlike return codes - unfortunately don't seem to be of much interest for SG14).

As for the working proof: I am very reluctant to write pull requests which are guaranteed to be rejected. Still, here is a sketch of how it can be done (note that I don't really expect meaningful comments - my apologies, I can't count "The Almighty SG14 Has Already Spoken" as meaningful - but here it is if even just for the sake of completeness):

a. take your lib as a base
b. implement copy constructor of errored_status_code<erased> using domain's _do_erased_copy() (I assume _do_erased_copy() is there for a reason)
c. say that handling of legacy C++ exceptions is to be done by two "legacy" domains: "copyable legacy" and "non-copyable legacy" (separated via is_copy_constructible())
c1. "copyable legacy" domain would use a pointer to an analogue-of-atomic_refcounted_string_ref (for 'proof', a pointer to an allocated shared_ptr<> will do)
c1.1 then, _do_erased_copy() for this "copyable legacy" domain becomes trivial.
c1.2 however, to put that legacy exception into shared_ptr<>-like thing will require copying, so a copy constructor for legacy exception is necessary here (and this approach won't work for non-copyable ones).
c2. "non-copyable legacy" domain would implement _do_erased_copy() creating a pre-defined exception saying "a copy of non-copyable legacy exception".
d. NB: as this is a purely additional capability - it won't affect the way how unwounding works, at all.

The only really ugly thing here is "non-copyable legacy" stuff, but OTOH it is already extremely rare, and on the plus side the design above doesn't carry restrictions-caused-by-legacy-stuff forever-and-ever; also, it separates concerns between new-shiny-exceptions and handling-legacy-stuff-within-them very clearly. Of course, as always, pros-and-cons balance is arguable , but personally I would vote for having std::error copyable (which, as we already agreed, is what-developers-want) even at certain costs to those converting legacy C++ exceptions into std::error (but only to legacy ones!, so those concerned about performance on failure path, won't be affected!); IMNSHO, certain migration costs are inevitable, and usually it is more important to keep them transient rather than to suffer from migration-induced restrictions indefinitely. BTW, the performance costs of this extra allocation (for copyable legacy) are extremely minor: on x64 malloc()/free() pair takes as little as 25 CPU cycles these days, which is like 2 orders of magnitude less than a typical handling of legacy C++ exception.

And at least for me it clearly means that this discussion-which-not-happened-yet (on specifics of std::error) should take place somewhere (probably in WG21 as exceptions - unlike return codes - unfortunately don't seem to be of much interest for SG14).

You seem to have some weird fixation with std::error. It's just one part of an overall framework which hangs together.

You may be assuming that dynamic exceptions get dropped from the language? P0709 never says this: the syntax and semantics of dynamic exceptions will always remain for backwards source compatibility. So if you want to throw a copyable type, and for some reason you don't want to throw a typed status code (which is copyable if its payload is copyable), you can always throw a dynamic rather than static exception to get the current exception throw semantics.

(What happens under the bonnet as implementation is a separate issue. Dynamic exception syntax and semantics will be preserved, even if implemented in practice as static exceptions)

Now, to refute your proposed alternative design point by point:

implement copy constructor of errored_status_code using domain's _do_erased_copy() (I assume _do_erased_copy() is there for a reason)

As I explained before, that would make status code's copy constructor non-trivial, and thus prevent it being treated as trivially copyable.

You may not be aware that compilers treat trivially copyable types very differently from non-trivially copyable types. They are optimised "as if" they are C rather than C++ types. std::error_code has an irritating design precisely to ensure it is trivially copyable. So does status code and error, or rather, we have slightly extended the concept of trivially copyable to include move relocating, which we (as in SG14) think is doable (though compiler vendor representatives on WG21 think may be problematic).

There is very widespread agreement on WG21 that std::error needs to be trivially copyable at minimum, even if move relocating gets ruled by SG12 as not possible under the C++ standard object and memory model.

however, to put that legacy exception into shared_ptr<>-like thing will require copying, so a copy constructor for legacy exception is necessary here (and this approach won't work for non-copyable ones).

You really don't need to worry about how to emulate dynamic exceptions using the static exception mechanism. Literally an implementation detail. I have an example of implementation at https://github.com/ned14/status-code/blob/master/example/thrown_exception.cpp, but that's really a committee worry. If static exceptions get standardised, AND if the committee and compiler vendors think it should be possible to emulate dynamic exceptions using static exceptions, AND if the committee thinks the two exception implementations should interoperate, THEN we are into that discussion. But that's a discussion not likely to happen until 2021, at the earliest. We haven't even gained committee assent to P0709 in principle yet, which would be a prerequisite to the development of prototype compiler support.

personally I would vote for having std::error copyable (which, as we already agreed, is what-developers-want) even at certain costs to those converting legacy C++ exceptions into std::error (but only to legacy ones!, so those concerned about performance on failure path, won't be affected!)

Nobody will need to convert code. Under P1095, the STL would in the future throw std::error, but they could be caught using the existing dynamic exception catch clauses so all existing source code would continue to work perfectly. With a compiler switch, one could recompile one's existing code to implement dynamic exceptions using static exceptions without source code changes. So no need to convert code!

But if you did want to convert to static exceptions, then it is very right and proper that you need to refactor your code on a function by function basis. Static exceptions are not free - they may reduce performance in the success path on some CPUs in exchange for determinism on the failure path. There are plenty of codebases which will find that unacceptable, and wish to retain zero overhead in the success path. Those codebases ought to remain on dynamic exceptions. Indeed, this is a function by function issue, P1095 assumes people will use static exceptions where failure path determinism is very important, and dynamic exceptions where absolute success path performance is critical.

Under P1095, the static vs dynamic exception throw mechanisms remain distinct, as each solves a different problem. Even if it all ends up static exception throw under the bonnet, you are still hinting strongly to the compiler whether failure code paths are expected to be frequently used or not. Any decent compiler would arrange code layout appropriately.

You seem to have some weird fixation with std::error. It's just one part of an overall framework which hangs together.

Let's not try to misinterpret my words. I merely think that ALL parts of the standard should be thought out (and yes, it should include even those parts which you don't need for your outcome library). If you don't care about std::error - that's fine, but somebody certainly should. I really hope that you won't try to argue further on this point.

As I explained before, that would make status code's copy constructor non-trivial, and thus prevent it being treated as trivially copyable.

To be precise: not the copy constructor of status_code (BTW, status code<erased> has it's copy constructor explicitly deleted), but the one of errored_status_code.

More importantly, even copy constructor of errored_status_code<erased> doesn't really exist (it cannot be generated in spite of being declared as 'default' due to copy constructor being deleted from its base class status_code<erased>). And as copy constructor of errored_status_code<erased> doesn't really exist - adding it cannot possibly make this class from trivially-copyable into non-trivially-copyable; instead, adding its copy constructor as I suggest, makes it non-trivially-copyable from non-copyable-at-all(!)

There is very widespread agreement on WG21 that std::error needs to be trivially copyable at minimum

  1. Do you care to show any documents illustrating this claim of yours? (just as one example, P0709 mentions "trivially relocatable" as The Requirement).

  2. Side note: if "trivially copyable" is minimum, I'm wondering what can be "maximum".

  3. Most importantly: your std::error is not copyable at all (as you yourself have said above, "proposed error is move-only"(!); BTW, if you remember, std::error being non-copyable is the whole point of this issue in the first place) => hence, it cannot possibly be "trivially copyable" at any rate. Make std::error copyable (trivially or not) - and it will become catchable-by-value, making me perfectly happy (and closing this very issue not as WONTFIX, but as FIXED).