[N4152] uncaught_exceptions result type is signed

65 views
Skip to first unread message

Andrey Semashev

unread,
Jul 26, 2015, 4:06:36 PM7/26/15
to std-dis...@isocpp.org
Hi,

I'm wondering why std::uncaught_exceptions() result type is int (i.e.
signed). The negative values don't make sense and are not described in
N4152 or N4527. If only non-negative values are valid, can the type be
changed to an unsigned integer? If negative values are valid then I
believe they should be documented.

Another (mostly academic) concern I have is the size of the integer. I
admit I'm not closely familiar with compiler implementations but I
imagine that in order to support multiple exceptions in flight (i.e.
points when std::uncaught_exceptions() > 1) the implementation has to
implement a list of the exceptions, or perform stack unwinding
recursively. Either way, the number of nested exceptions seems to be
limited only by memory (at least, I didn't find any other limits in the
standard), and as such std::uncaught_exceptions() returning std::size_t
seems more appropriate.

I understand that unsigned int would suffice for most practical uses on
the current platforms, but I still think the choice of types in the
standard interfaces should have some formal grounds.

dyp

unread,
Jul 26, 2015, 5:01:57 PM7/26/15
to std-dis...@isocpp.org

Andrey Semashev

unread,
Jul 27, 2015, 7:04:54 AM7/27/15
to std-dis...@isocpp.org
Thank you for the link.

I must say I disagree with the reasons presented there. I won't touch
here the discussion of whether uncaught_exceptions() is the appropriate
interface for scope guard/transaction implementation. I'll just say that
this interface is enough for my needs currently.

First of all, and most importantly, I believe interface clarity is what
matters most, especially in the case of standard interfaces. I have said
in the OP that using a signed type implies that the function can return
negative values but the meaning of these values is not defined nor can
be guessed. The current interface is less clear than it can (and
should), if not confusing.

Secondly, since negative values are possible and don't make sense, good
code will have to check the returned value before using it. The error
handling can be case-specific, most likely an exception or assert/abort.
In any case, this effectively nullifies any possible gain from
optimizations the compiler might apply. The linked discussion didn't
present what such optimizations might be, but I don't believe they would
have any measurable effect in 99% cases. If the effect is measurable and
it matters, it is better to resort to assembler anyway (or redesign your
code). Note: I'd like to stress that I'm talking about the effect
compared to a similar code operating on unsigned integers.

Thirdly, sloppy code will omit the check for negative values, and I
believe this will be the most common case. It might not matter with the
simplest uses of uncaught_exceptions() (i.e. just compare the two ints
for inequality in destructor) but it is possible that more complex uses
appear where negative values are harmful. It is easy to forget the
check, and as such the interface is error prone.

Lastly, regarding the guideline to use signed integers for regular math
and unsigned for bit operations. I didn't find any technical reason for
this distinction. Overflows happen with both signed and unsigned
integers, and in general I'd say there are just as many ways to screw up
with signed integers as there are with unsigned. New users will have to
learn both signed and unsigned integers anyway, I don't see a problem here.

A little lyrical digression. I have always thought of signed integers as
of unsigned integers having an additional property of being negative. I
know there are differences in behavior in different contexts, but to me
the sign is an optional property which should be used only when
required. And it so happens that most of the math I do in code is with
positive integers. It is only natural not to use signed integers for
that because doing so would be confusing and misguiding for the readers
of the code. When you use a signed integer, you have to always be aware
of its sign, which is another thing to track in mind, and this is
unnecessary work that can be avoided with unsigned integers. This is
akin to floating point numbers with which you also have to be aware of
rounding and computation error. Having signed integers in the standard
library where unsigned should have been will surely complicate my work
as I will have to add checks for negative values and cast to unsigned.

Tony V E

unread,
Jul 28, 2015, 9:24:54 AM7/28/15
to std-dis...@isocpp.org
On Mon, Jul 27, 2015 at 7:04 AM, Andrey Semashev <andrey....@gmail.com> wrote:
Thank you for the link.

I must say I disagree with the reasons presented there. I won't touch here the discussion of whether uncaught_exceptions() is the appropriate interface for scope guard/transaction implementation. I'll just say that this interface is enough for my needs currently.

First of all, and most importantly, I believe interface clarity is what matters most, especially in the case of standard interfaces. I have said in the OP that using a signed type implies that the function can return negative values but the meaning of these values is not defined nor can be guessed. The current interface is less clear than it can (and should), if not confusing.

Secondly, since negative values are possible and don't make sense, good code will have to check the returned value before using it. The error handling can be case-specific, most likely an exception or assert/abort. In any case, this effectively nullifies any possible gain from optimizations the compiler might apply. The linked discussion didn't present what such optimizations might be, but I don't believe they would have any measurable effect in 99% cases. If the effect is measurable and it matters, it is better to resort to assembler anyway (or redesign your code). Note: I'd like to stress that I'm talking about the effect compared to a similar code operating on unsigned integers.

Thirdly, sloppy code will omit the check for negative values, and I believe this will be the most common case. It might not matter with the simplest uses of uncaught_exceptions() (i.e. just compare the two ints for inequality in destructor) but it is possible that more complex uses appear where negative values are harmful. It is easy to forget the check, and as such the interface is error prone.

Negative values are NOT possible.  I'm sure the specification will say so.  If you add checks for < 0, your compiler can optimize that code right out, and/or give you a warning for unreachable code.


Lastly, regarding the guideline to use signed integers for regular math and unsigned for bit operations. I didn't find any technical reason for this distinction. Overflows happen with both signed and unsigned integers, and in general I'd say there are just as many ways to screw up with signed integers as there are with unsigned. New users will have to learn both signed and unsigned integers anyway, I don't see a problem here.

I don't usually like to argue just based on "appeal to authority", but when you have a panel of experts like that, it is hard to disagree with them.
For me, I had already adopted the same rules years ago - if you need a number, use int.  For the same reasons - too many times I had tried to use unsigned (ie width and height of an image, etc), but it always bites you somewhere (ie doing math on image intersection).  It is not the value itself that is a problem, it is how it interacts with other values when you try to use it.

Maybe uncaught_exception() is an exception to the rule, but I doubt it.  Sure the number returned is always positive, but when you then go to use it, you want it to act like a number, not a number mod 2^n.

 
A little lyrical digression. I have always thought of signed integers as of unsigned integers having an additional property of being negative. I know there are differences in behavior in different contexts, but to me the sign is an optional property which should be used only when required. And it so happens that most of the math I do in code is with positive integers. It is only natural not to use signed integers for that because doing so would be confusing and misguiding for the readers of the code. When you use a signed integer, you have to always be aware of its sign, which is another thing to track in mind, and this is unnecessary work that can be avoided with unsigned integers. This is akin to floating point numbers with which you also have to be aware of rounding and computation error. Having signed integers in the standard library where unsigned should have been will surely complicate my work as I will have to add checks for negative values and cast to unsigned.


On 27.07.2015 00:01, dyp wrote:
Please see
https://groups.google.com/a/isocpp.org/d/topic/std-discussion/6UH9pMfh9xk/discussion

On 26.07.2015 22:06, Andrey Semashev wrote:
Hi,

I'm wondering why std::uncaught_exceptions() result type is int (i.e.
signed). The negative values don't make sense and are not described in
N4152 or N4527. If only non-negative values are valid, can the type be
changed to an unsigned integer? If negative values are valid then I
believe they should be documented.

Another (mostly academic) concern I have is the size of the integer. I
admit I'm not closely familiar with compiler implementations but I
imagine that in order to support multiple exceptions in flight (i.e.
points when std::uncaught_exceptions() > 1) the implementation has to
implement a list of the exceptions, or perform stack unwinding
recursively. Either way, the number of nested exceptions seems to be
limited only by memory (at least, I didn't find any other limits in the
standard), and as such std::uncaught_exceptions() returning std::size_t
seems more appropriate.

I understand that unsigned int would suffice for most practical uses on
the current platforms, but I still think the choice of types in the
standard interfaces should have some formal grounds.



--

--- 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/.

Andrey Semashev

unread,
Jul 28, 2015, 10:11:25 AM7/28/15
to std-dis...@isocpp.org
So the fact that it doesn't currently can be considered a defect?

> If you add checks for < 0, your compiler can optimize that code
> right out, and/or give you a warning for unreachable code.

If the returned value stays signed, I don't see why the compiler would
do that. That is as long as the type is not augmented with some
compiler-specific magic, telling that it never becomes negative. But
then again, instead of resorting to such magic it could have used an
unsigned type in the first place.

> Lastly, regarding the guideline to use signed integers for regular
> math and unsigned for bit operations. I didn't find any technical
> reason for this distinction. Overflows happen with both signed and
> unsigned integers, and in general I'd say there are just as many
> ways to screw up with signed integers as there are with unsigned.
> New users will have to learn both signed and unsigned integers
> anyway, I don't see a problem here.
>
>
> https://channel9.msdn.com/Events/GoingNative/2013/Interactive-Panel-Ask-Us-Anything
>
> I don't usually like to argue just based on "appeal to authority", but
> when you have a panel of experts like that, it is hard to disagree with
> them.

Yes, I've seen this video. And with all due respect to the experts, the
rationale for the given guideline did not seem convincing to me (which I
expressed in the quoted paragraph above).

> For me, I had already adopted the same rules years ago - if you need a
> number, use int. For the same reasons - too many times I had tried to
> use unsigned (ie width and height of an image, etc), but it always bites
> you somewhere (ie doing math on image intersection). It is not the
> value itself that is a problem, it is how it interacts with other values
> when you try to use it.

Interesting you say so, because I developed an image manipulation
library and I used unsigned integers everywhere. I can't remember any
problems with them.

> Maybe uncaught_exception() is an exception to the rule, but I doubt it.
> Sure the number returned is always positive, but when you then go to use
> it, you want it to act like a number, not a number mod 2^n.

It only makes difference where the overflow happens, and it happens both
with signed and unsigned integers. One should keep the overflows in mind
either way, so I can't see how signed int helps here.


> A little lyrical digression. I have always thought of signed
> integers as of unsigned integers having an additional property of
> being negative. I know there are differences in behavior in
> different contexts, but to me the sign is an optional property which
> should be used only when required. And it so happens that most of
> the math I do in code is with positive integers. It is only natural
> not to use signed integers for that because doing so would be
> confusing and misguiding for the readers of the code. When you use a
> signed integer, you have to always be aware of its sign, which is
> another thing to track in mind, and this is unnecessary work that
> can be avoided with unsigned integers. This is akin to floating
> point numbers with which you also have to be aware of rounding and
> computation error. Having signed integers in the standard library
> where unsigned should have been will surely complicate my work as I
> will have to add checks for negative values and cast to unsigned.
>
>
> On 27.07.2015 00 <tel:27.07.2015%2000>:01, dyp wrote:
>
> Please see
> https://groups.google.com/a/isocpp.org/d/topic/std-discussion/6UH9pMfh9xk/discussion
>
> <mailto:std-discussion%2Bunsu...@isocpp.org>.
> To post to this group, send email to std-dis...@isocpp.org
> <mailto:std-dis...@isocpp.org>.
> --
>
> ---
> 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
> <mailto:std-discussio...@isocpp.org>.
> To post to this group, send email to std-dis...@isocpp.org
> <mailto:std-dis...@isocpp.org>.

Bo Persson

unread,
Jul 28, 2015, 10:25:54 AM7/28/15
to std-dis...@isocpp.org
The number of active exceptions cannot reasonably be negative. Perhaps
the committee found that obvious enough, that it doesn't have to be
stated explicitly.

It's not that the standard's text is too short...


>
>> If you add checks for < 0, your compiler can optimize that code
>> right out, and/or give you a warning for unreachable code.
>
> If the returned value stays signed, I don't see why the compiler would
> do that. That is as long as the type is not augmented with some
> compiler-specific magic, telling that it never becomes negative. But
> then again, instead of resorting to such magic it could have used an
> unsigned type in the first place.
>

The compiler might learn the semantics of a "std::"-function and
optimize based on that knowledge. Most compilers already do that for
things like memcpy and strlen.


Bo Persson


Ville Voutilainen

unread,
Jul 28, 2015, 10:31:52 AM7/28/15
to std-dis...@isocpp.org
On 28 July 2015 at 17:11, Andrey Semashev <andrey....@gmail.com> wrote:
>> Maybe uncaught_exception() is an exception to the rule, but I doubt it.
>> Sure the number returned is always positive, but when you then go to use
>> it, you want it to act like a number, not a number mod 2^n.
> It only makes difference where the overflow happens, and it happens both
> with signed and unsigned integers. One should keep the overflows in mind
> either way, so I can't see how signed int helps here.

A sanitizer can diagnose signed overflows, whereas it can't diagnose
unsigned overflows.

Andrey Semashev

unread,
Jul 28, 2015, 10:47:36 AM7/28/15
to std-dis...@isocpp.org
Then the sanitizer has to be improved. Unsigned overflows can be
detected just fine: if not with CPU flags then with the "(a + b) <
min(a, b)" check.

And I don't believe sacrificing the interface clarity is a reasonable
tradeoff for just one tool that is not smart enough (yet).

Ville Voutilainen

unread,
Jul 28, 2015, 10:52:18 AM7/28/15
to std-dis...@isocpp.org
On 28 July 2015 at 17:47, Andrey Semashev <andrey....@gmail.com> wrote:
> On 28.07.2015 17:31, Ville Voutilainen wrote:
>> A sanitizer can diagnose signed overflows, whereas it can't diagnose
>> unsigned overflows.
> Then the sanitizer has to be improved. Unsigned overflows can be detected
> just fine: if not with CPU flags then with the "(a + b) < min(a, b)" check.

How is the sanitizer supposed to know which of those overflows are intentional
and which ones aren't?

> And I don't believe sacrificing the interface clarity is a reasonable
> tradeoff for just one tool that is not smart enough (yet).

I don't believe having uncaught_exceptions() return an int sacrifices interface
clarity.

Andrey Semashev

unread,
Jul 28, 2015, 11:17:13 AM7/28/15
to std-dis...@isocpp.org
On 28.07.2015 17:52, Ville Voutilainen wrote:
> On 28 July 2015 at 17:47, Andrey Semashev <andrey....@gmail.com> wrote:
>> On 28.07.2015 17:31, Ville Voutilainen wrote:
>>> A sanitizer can diagnose signed overflows, whereas it can't diagnose
>>> unsigned overflows.
>> Then the sanitizer has to be improved. Unsigned overflows can be detected
>> just fine: if not with CPU flags then with the "(a + b) < min(a, b)" check.
>
> How is the sanitizer supposed to know which of those overflows are intentional
> and which ones aren't?

It doesn't, unless you tell it. So either it detects all overflows by
default unless you explicitly permit the particular ones or the other
way around.

Either way, there is no technical problem with it, and if it is deemed
so important to warrant changes to the standard I would rather have a
standard way to mark (un)intended unsigned overflows along with actions
upon ones than using signed integers instead of unsigned. Clang/gcc, for
instance, offer builtin intrinsics that allow to detect overflows.

Tony V E

unread,
Jul 28, 2015, 11:57:21 AM7/28/15
to std-dis...@isocpp.org
On Tue, Jul 28, 2015 at 11:17 AM, Andrey Semashev <andrey....@gmail.com> wrote:
On 28.07.2015 17:52, Ville Voutilainen wrote:
On 28 July 2015 at 17:47, Andrey Semashev <andrey....@gmail.com> wrote:
On 28.07.2015 17:31, Ville Voutilainen wrote:
A sanitizer can diagnose signed overflows, whereas it can't diagnose
unsigned overflows.
Then the sanitizer has to be improved. Unsigned overflows can be detected
just fine: if not with CPU flags then with the "(a + b) < min(a, b)" check.

How is the sanitizer supposed to know which of those overflows are intentional
and which ones aren't?

It doesn't, unless you tell it. So either it detects all overflows by default unless you explicitly permit the particular ones or the other way around.


By using an unsigned int, you are telling it you *want* wrap-around overflow. They are all intentional. That is the intent of unsigned.

 

Either way, there is no technical problem with it, and if it is deemed so important to warrant changes to the standard I would rather have a standard way to mark (un)intended unsigned overflows along with actions upon ones than using signed integers instead of unsigned. Clang/gcc, for instance, offer builtin intrinsics that allow to detect overflows.
--

--- 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.

Andrey Semashev

unread,
Jul 28, 2015, 12:16:56 PM7/28/15
to std-dis...@isocpp.org
On 28.07.2015 18:57, Tony V E wrote:
>
>
> On Tue, Jul 28, 2015 at 11:17 AM, Andrey Semashev
> <andrey....@gmail.com <mailto:andrey....@gmail.com>> wrote:
>
> On 28.07.2015 17:52, Ville Voutilainen wrote:
>
> On 28 July 2015 at 17:47, Andrey Semashev
> <andrey....@gmail.com <mailto:andrey....@gmail.com>>
> wrote:
>
> On 28.07.2015 17:31, Ville Voutilainen wrote:
>
> A sanitizer can diagnose signed overflows, whereas it
> can't diagnose
> unsigned overflows.
>
> Then the sanitizer has to be improved. Unsigned overflows
> can be detected
> just fine: if not with CPU flags then with the "(a + b) <
> min(a, b)" check.
>
> How is the sanitizer supposed to know which of those overflows
> are intentional
> and which ones aren't?
>
> It doesn't, unless you tell it. So either it detects all overflows
> by default unless you explicitly permit the particular ones or the
> other way around.
>
> By using an unsigned int, you are telling it you *want* wrap-around
> overflow. They are all intentional. That is the intent of unsigned.

I think you're making excessive assumptions. The only intention the type
'unsigned int' communicates is that its values are never negative.
Whether or not overflows are intended is defined by its use in the
surrounding code and semantics the developer puts in it.

Tony V E

unread,
Jul 28, 2015, 1:12:32 PM7/28/15
to std-dis...@isocpp.org
Maybe you need a RangedInt or SafeInt type.

A type is defined by all its behaviour.  You use that type intentionally, you get that behaviour intentionally.

The intent of a signed int is to *never* overflow - that's undefined behaviour, so it must not be the intent.
Unsigned int has well defined overflow behaviour.  So it is either the intent or it at least may be the intent.

Sure, maybe I'm being excessive.

It is hard to explain why so many experts think int is better than unsigned int.  It really just comes down to experience. (Or maybe religion?)  I think part of the problem is that C (and C++) allows signed to unsigned conversion.  If it didn't, it might be easier to use unsigned without surprises.

In addition to the expert panel above, when uncaught_exception() was discussed in the committee (in theory, a room full of experts), signed vs unsigned was explicitly discussed, and voted on.  Actually, there wasn't much discussion - everyone understands the debate already.  There were _some_ votes for unsigned, but there were _very many_ more for signed.

I do think you can (maybe even should) be vigilant when working with both signed and unsigned, and if you are vigilant, you can use both.  But eventually I gave up and just started using signed everywhere - still being vigilant, and my life was simpler.


I think asking for reasoning behind the decision on uncaught_exception() is valid, and I think our answers are lacking - "experience" and "trust us" aren't the best answers.  But still valid.  (Maybe "unsigned/signed conversion sucks" is the start of an answer?)

Reply all
Reply to author
Forward
0 new messages