On 2015–06–17, at 8:40 AM, FrankHB1989 <frank...@gmail.com> wrote:A few more notes:
1. The type `int` can be natural than others in 1970s, but should be not any longer now.
2. Even if the fundamental types in C++ are not graceful, they deserved to be use in the most suitable places, esp. by the standard library.
Specifically, I dislike to weaken the range of the types, i.e. using integer types instead of unsigned types merely due to one or a few special values are reserved to indicate exceptional states. This can be rarely "natural".
Violation of this can be treated as a case of "no overhead principle": almost a half of the range is dropped (which may be avoid by other style, e.g. using exception instead of error code), and the result is not obviously beneficial.
3. I'm in doubt whether a realistic implementation has more opportunity to perform optimization on signed types than unsigned types, once the unsigned types are used properly, considering what would be optimized away. Are there any examples?
On 2015–06–17, at 10:25 AM, David Krauss <pot...@gmail.com> wrote:public:unwinding_state() noexcept; // Initialize with the current state.unwinding_state( unwinding_state const & ) noexcept;unwinding_state & operator = ( unwinding_state const & ) noexcept;
On 2015–06–17, at 1:00 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:The wrapper you outline doesn't magically solve the problem of moving
a transaction
to a different lifetime scope. When a move operation occurs, the user
still needs to
signal to the transaction whether it was or was not moved to a different scope.
And the user still needs to "conform to the procotol" as in initialize
the wrapper at
the beginning of a transaction and check it at the end.
On 2015–06–17, at 8:40 AM, FrankHB1989 <frank...@gmail.com> wrote:A few more notes:
1. The type `int` can be natural than others in 1970s, but should be not any longer now.int is effectively 16-bit on “small” systems, 32-bit on the majority of systems with virtual memory, and perhaps 64-bit on some systems that don’t support smaller datatypes very well. The landscape really hasn’t changed much, except that small systems are now less often overextended to do big jobs.Perhaps you can’t draw very strong conclusions from sizeof(int), but it’s still a good enough proxy for the complexity of programs that the machine is likely to run. In the case of uncaught_exceptions, a system with 16-bit int is very likely to hit some other limit before getting to 30,000 nested unwindings, but a system with 32-bit int might not.
2. Even if the fundamental types in C++ are not graceful, they deserved to be use in the most suitable places, esp. by the standard library.
Specifically, I dislike to weaken the range of the types, i.e. using integer types instead of unsigned types merely due to one or a few special values are reserved to indicate exceptional states. This can be rarely "natural".
Violation of this can be treated as a case of "no overhead principle": almost a half of the range is dropped (which may be avoid by other style, e.g. using exception instead of error code), and the result is not obviously beneficial.Because you plan to use 2 billion nested unwindings… but not 5 billion?
Really, 30,000 is enough for anyone. It already indicates that the program is crashing. The issue at hand is how to represent a small number with minimal constraints. The answer is int.
3. I'm in doubt whether a realistic implementation has more opportunity to perform optimization on signed types than unsigned types, once the unsigned types are used properly, considering what would be optimized away. Are there any examples?The usage of uncaught_exceptions is too simple for the instructions to be optimized. It simply reads a thread-local global. You store that in a structure. Later you call it again and compare to the previous result. The comparison feeds into a conditional branch or move. Done.However, it does get stored in a structure in the meantime. It would be nice to maximize the chance of uncaught_exceptions fitting into padding bytes, for example in a class that would otherwise be empty. I think that makes a decent argument for some flavor of char. But…The elephant in the room, which I can’t believe hasn’t been pointed out among so many software engineering experts, is that uncaught_exceptions is obviously missing encapsulation. It requires the user to follow a protocol which is far from obvious. What users need is an object which stores the unwinding level at its construction and then later tells you whether the unwinding level is still the same. Nobody should hand-code this process as boilerplate! Moreover, there is no other valid usage of uncaught_exceptions, and any attempt at cleverness is certain to end in disaster. An integer of any type is the wrong interface.Brittleness is the other consequence of poor encapsulation. Exception processing can get complicated and we don’t have a theory as to why uncaught_exceptions is the ultimate answer to the problem. It’s just a variable that was conveniently lying around. Are we sure that the implementation will never, ever want to extend it? For example,1. Keeping a pointer to the current unwinding exception would allow the debugger to provide insight into transactions initiated by error handling.2. It’s strictly limited to RAII classes. uncaught_exceptions assumes that the transaction is created on the stack, so it must be destroyed before unwinding crosses the scope of its construction. What if its lifetime extends further, because it can’t certainly be committed in RAII fashion? What about a server that creates transactions on one thread and retires them on another? It should be possible to transplant the exception processing state by catching the exception and re-throwing it elsewhere. std::exception_ptr allows this but uncaught_exceptions does not. Even worse, comparing small integers provides arbitrary results in such a situation. If this ever leads to a security hole in an application, standard library implementations won’t be able to fix it until ISO revises the API.By far the sensible thing to do is to introduce a class:class unwinding_state {// Implementation-defined nonstatic member(s).// (Let an int storing uncaught_exceptions() be a conforming implementation.)public:unwinding_state() noexcept; // Initialize with the current state.unwinding_state( unwinding_state const & ) noexcept;unwinding_state & operator = ( unwinding_state const & ) noexcept;enum class status_type {normal,exceptional};status_type status() const;operator bool () const{ return status() == status_type::normal; }};We use C++ because it provides this sort of abstraction with zero overhead. If you want to code without abstraction, but instead always obsessing over interfaces hardcoded to to int, and worrying whether everything is simultaneously micro-optimized and future-proofed, use C.
Thank you for the answer.
I carefully checked these reasons and I am sure I have thought all of them previously.
To summarize shortly: plausible, but not totally convinced me.
I agree:
0. Obviously, `int` is shorter.
1. The type `int` is "natural" over other signed types in general.
(So again, is it accidental to choose `long` for std::shared_ptr::use_count?)
2. Both of them are needed (Andrei Alexandrescu's point).
3. Signed integers have advantages to represent negative results naturally, i.e. they can be clearer.
4. Signed integers make newbies (or perhaps average users) less surprise on integer arithmetic, as well as, some other arithmetic operations e.g. pointer subtraction.
5. The performance difference between signed and unsigned arithmetic operations is generally insignificant for users of modern architectures, even though the cost on hardware implementation may differ.
6. No simple guideline (again Andrei Alexandrescu's point), since they are more complicated than pure integer arithmetic rules most of users learned at school, and the requirements differ.
7. Be careful to implicit integer conversions, or better try to avoid them (Bjarne Stroustrup's point). (I almost always use -Wsign-conversion, etc when possible.)
However:
0. Although shorter is generally good, typing less is usually not in the first place. If other things matter, it can be changed relatively easily.
For example, once we have `main(){}`, then we forbade omission of `int`.
1. The situation of unsigned integers is same. I'd rather reluctant to use `unsigned long` instead of `unsigned`, unless really needed (e.g. to prevent `DWORD` pollution).
2. Both of them are needed (Andrei Alexandrescu's point), but usually in different cases.
3. Signed integers may be clearer than unsigned integers to use, only when negative integer values are really needed.
4. Despite the truth of unfamiliarity on modular arithmetic for average users, many people have even no such knowledge: "integers overflow is undefined behavior" or "unsigned integers never overflow". The integers in C++ is actually not the same in mathematics, whether signed or not, since the precision is limited and there exist values out of range to be handled. This innocent is dangerous. Why cheating users with the fictional simplicity?
5. Anyway, signed integers are actually more complicated to implement. Sign bit is special, though we have all kinds of complexity on other circuits, they beats unsigned integers easily. Therefore at least I don't think signed integers are superior here.
6. To be pragmatic, "use one single type as default" rule is not enough for API design. To me that sounds like "a common base class for every type is always good" ("by default"), which is not the fact. It may get users like me confused about the reasoning: why use some particular type even if there exist at least one more type to fit the intent more precisely, or what does the designer of the API want the user to care. In such cases, signed integers are definitely unclearer.
7. Despite conversion, the sign has already bring traps on portability. Are they 2's complements, 1’s complement or signed magnitude representations? How to reproduce the strange behavior if they have probably overflowed? ...
8. As the name, "unsigned integers" are for special "unsigned" integer arithmetic, rather than being specific to bit patterns. Even if the latter is needed, there can be clearer ways, though facilities derived from C (bit fields) are not ideal. The abstraction of bitmask types may be a good beginning.
Currently we are lack of more powerful features (i.e. Scheme's numeric tower) to teach users always "do the right thing" easily, but it should not the case forever. Thus we may have more compatibility issues in future which we'd have avoid. To make the evolution harder is not intended, is it?
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
On Tuesday 16 June 2015 17:40:12 FrankHB1989 wrote:
> 3. I'm in doubt whether a realistic implementation has more opportunity to
> perform optimization on signed types than unsigned types, once the unsigned
> types *are used properly*, considering *what would be optimized away*. Are
> there any examples?
Your doubt is unfounded. Clearly and provably the compiler can perform more
optimisations on int than on unsigned.
That's also the reason why GCC needs to get rid of the warning that says
"assuming signed overflow does not occur when assuming that (X + c) < X is
always false". That warning is triggered when GCC *did* optimise the code,
which often is the intended behaviour. Getting rid of the warning by changing
the code implies pessimising the code by switching to an unsigned type: the
compiler now will now need to check for overflow.
The warning was introduced when GCC started doing the optimisation and someone
complained (loudly) in a bug report that the compiler shouldn't behave like
that.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358
On 2015–06–17, at 3:51 PM, FrankHB1989 <frank...@gmail.com> wrote:Perhaps the major problems is difficulties during standardization. Even if your proposed interface is more usable to ordinary users, someone may still need the portable underlying low level interface.
And there might be more different models of higher level abstraction. Taking account all of these stuff, discussion, wording, balloting ... needs time.
On 2015–06–17, at 5:29 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:Will it reset the state on move?
So it encapsulates storing of the uncaught_exceptions value and provides
encapsulation for checking the current count against that. That's
nice, but hardly
crucial.
On 2015–06–17, at 5:33 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:Such transactions are not the purpose of uncaught_exceptions. If your
commit/rollback
operations throw, they still shouldn't throw out of destructors.
If the destructor observes a non-committed state, then it infers unwinding
and performs rollback. This is the safe status quo and there’s no need for
uncaught_exceptions.
I fail to follow that logic.
There are cases where nested transactions
want to commit
even during unwinding,
and there are cases where they want to rollback during
unwinding,
and uncaught_exceptions provides a way to reliably detect unwinding,
so the various strategies can actually be built on top of it.
If you don’t want to write commit() at the end of each scope, you need
throwing destructors. That is what this is all about, since the beginning,
no?
No. I don't need throwing destructors for automatic commit/rollback.
On 17.06.2015 01:53, FrankHB1989 wrote:
Thank you for the answer.
I carefully checked these reasons and I am sure I have thought all of them previously.
To summarize shortly: plausible, but not totally convinced me.
It is perfectly fine, to be unconvinced by the reasoning, but you asked about why it is the way it is and this is your answer.
The reasons on both sides are well known, and the debate has been had multiple times.
When the design (of uncaught_exceptions) was reviewed this was debated again (shortly) and voted on.
Only very few people voted for unsigned.
/cc Nevin because he’s shown an interest in throwing destructors.On 2015–06–17, at 3:51 PM, FrankHB1989 <frank...@gmail.com> wrote:Perhaps the major problems is difficulties during standardization. Even if your proposed interface is more usable to ordinary users, someone may still need the portable underlying low level interface.No, the high-level interface is fundamentally more portable.
And there might be more different models of higher level abstraction. Taking account all of these stuff, discussion, wording, balloting ... needs time.Laziness is not an excuse for standardizing the wrong interface.
I’m starting to get the impression that uncaught_exceptions is not intended to support transaction objects at all, but only scope guards encapsulating success and failure cases. So the best interface would rather be a scope guard facility, but that hasn’t happened yet, probably because 1) it would need anonymous guard variables which the core language still doesn’t have, and 2) the proposals so far haven’t really been palatable.For what it’s worth, I do use scope guards occasionally, but seldom need to cancel one. If the need arises, I just declare a local bool cancel = false; , capture it into the guard, set it manually, and use if (cancel) in the guard destructor.If I want to run cleanup only on unwinding, I just write try { risky_business(); } catch (...) { cleanup(); throw; }. No guard, no boilerplate, no gimmicks. The strange thing about SCOPE_SUCCESS and SCOPE_FAIL is that they’re redundant with adding something to the end of the scope, and to a catch(...) block, respectively. The only reason to use them is aesthetic preference for grouping the success, failure, and cleanup blocks. But… aesthetically shuffling blocks of text is a job for the preprocessor, and these libraries already need the preprocessor to declare the anonymous variable and to save the user from writing the parens of a lambda expression.
On 2015–06–17, at 6:31 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:Because there are cases where the state needs to be reset and cases where
it doesn't need to be, so there are more than one kind of such wrappers that
domain-specific users want to write.
Even so, commits shouldn't throw out of destructors, even with
uncaught_exceptions.
Except that such manual commits can happen during unwinding.
“Rollback during unwinding” seems to be code for “commit in the destructor
when not unwinding.”
Well, having the capability to know when unwinding is in progress for
the current
scope allows making decisions based on it. I fail to see what's so
hard to understand
about that.
It’s as reliable as uncaught_exceptions is, which means no support for
rethrowing an exception_ptr. See my original message.
How does this wrapper help with that?
No. I don't need throwing destructors for automatic commit/rollback.
You need a destructor which throws if commitment does. I’m not familiar with
No I don't. I can design the system in a fashion somewhat similar to
what streams
do, so that the engine that executes transactions reports an error
state if it has
failed a transaction.
在 2015年6月17日星期三 UTC+8下午5:30:28,David Krauss写道:/cc Nevin because he’s shown an interest in throwing destructors.